package com.xyy.saas.payment.adpater.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.pingan.openbank.api.sdk.client.ApiClient;
import com.pingan.openbank.api.sdk.common.http.HttpResult;
import com.pingan.openbank.api.sdk.entity.FileRequest;
import com.pingan.openbank.api.sdk.entity.SdkRequest;
import com.pingan.openbank.api.sdk.exception.OpenBankFileNotFoundException;
import com.pingan.openbank.api.sdk.exception.OpenBankSdkException;
import com.pingan.openbank.api.sdk.exception.OpenBankSdkRunTimeException;
import com.sdb.util.Base64;
import com.sdb.util.SignUtil;
import com.xyy.saas.payment.adpater.PaymentAdapter;
import com.xyy.saas.payment.adpater.assemble.FBankRequestParamAssemble;
import com.xyy.saas.payment.adpater.assemble.dto.WithdrawAccountDto;
import com.xyy.saas.payment.adpater.pingan.PinganMethod;
import com.xyy.saas.payment.common.Constants;
import com.xyy.saas.payment.common.enums.AmountTypeEnum;
import com.xyy.saas.payment.common.enums.SeparateOrderAsyncTradeStatus;
import com.xyy.saas.payment.common.exception.PaymentException;
import com.xyy.saas.payment.common.exception.RetryException;
import com.xyy.saas.payment.cores.bo.*;
import com.xyy.saas.payment.cores.enums.*;
import com.xyy.saas.payment.cores.param.*;
import com.xyy.saas.payment.cores.vo.*;
import com.xyy.saas.payment.dao.model.*;
import com.xyy.saas.payment.merchant.core.dto.*;
import com.xyy.saas.payment.merchant.core.service.BaseBankService2;
import com.xyy.saas.payment.merchant.core.service.SeparateOrderService2;
import com.xyy.saas.payment.merchant.core.service.UserInfoService2;
import com.xyy.saas.payment.merchant.core.vo.PaymentAuthQueryVo;
import com.xyy.saas.payment.merchant.core.vo.PaymentAuthVo;
import com.xyy.saas.payment.merchant.core.vo.UserDto;
import com.xyy.saas.payment.merchant.core.vo.UserInfoVo;
import com.xyy.saas.payment.payment.core.bo.RefundBO;
import com.xyy.saas.payment.payment.core.bo.WithdrawalBO;
import com.xyy.saas.payment.payment.core.dto.PayDto;
import com.xyy.saas.payment.payment.core.dto.PrepayDto;
import com.xyy.saas.payment.payment.core.dto.RefundDto;
import com.xyy.saas.payment.payment.core.dto.WithdrawalDto;
import com.xyy.saas.payment.payment.core.service.PrePayOrderService;
import com.xyy.saas.payment.service.*;
import com.xyy.saas.payment.trace.AlarmService;
import com.xyy.saas.payment.trace.DingTalkMessage;
import com.xyy.saas.payment.util.*;
import com.xyy.saas.payment.util.retry.RetryStrategy;
import com.xyy.saas.payment.util.retry.RetryTradeType;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 平安
 */
@Service
@Slf4j
public class PinganAdapter implements PaymentAdapter {
    @Value("#{${pingan.scence.map}}")
    private Map<String,String> pinganScenceMap;
    @Value("${pingan.code.switch:false}")
    private Boolean pinganCodeSwitch;

    @Value("${pingan.queryTradeStatusMock:false}")
    private Boolean queryTradeStatusMock;
    @Value("${pingan.queryTradeStatusMockError:false}")
    private Boolean queryTradeStatusMockError;

    @Value("${pingan.accountTransferMock:false}")
    private Boolean accountTransferMock;
    @Value("${pingan.accountTransferMockError:false}")
    private Boolean accountTransferMockError;

    @Value("${pingan.credit.refund.alarm:true}")
    private Boolean pinganCreditRefundAlarm;
    @Value("${pingan.credit.settle.alarm:true}")
    private Boolean pinganCreditSettleAlarm;
    @Value("${refundCreditPayV2.switch:false}")
    private Boolean refundCreditPayV2Switch;
    @Value("${refundCreditPayV2.intercept.switch:false}")
    private Boolean refundCreditPayV2InterceptSwitch;
    @Value("${refund.intercept.maxCount:2}")
    private int refundInterceptMaxCount;
    @Value("#{'${refundCreditPayV2Gray.refundNos:}'.split(',')}")
    private List<String> refundCreditPayV2GrayRefundNos;

    @Value("${asyncSettlementAndPoll.maxCounts:3}")
    private int asyncSettlementAndPollMaxCounts;
    @Value("${asyncCancelSettlementAndPoll.maxCounts:3}")
    private int asyncCancelSettlementAndPollMaxCounts;
    @Value("${asyncAccountTransferAndPoll.maxCounts:3}")
    private int asyncAccountTransferAndPollMaxCounts;
    @Value("${asyncUnfreezeMembershipAndPoll.maxCounts:3}")
    private int asyncUnfreezeMembershipAndPollMaxCounts;
    @Value("${asyncTransferRefundAndPoll.maxCounts:3}")
    private int asyncTransferRefundAndPollMaxCounts;

    // 实例化平安API对象
    private static ApiClient apiClient = ApiClient.getInstance(ApolloUtil.getProperty("pingan.configPath"));

    @Resource
    private PrePayOrderService prePayOrderService;
    @Resource
    private LeafUtil leafUtil;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private UserService userService;
    @Resource
    private UserInfoService2 userInfoService2;
    @Resource
    private ThirdUserInfoService thirdUserInfoService;
    @Resource
    private FBankRequestParamAssemble paramAssemble;
    @Resource
    private SeparateOrderService separateOrderService;
    @Resource
    private SeparateOrderService2 separateOrderService2;
    @Resource
    private BaseBankService2 baseBankService2;
    @Resource
    private PayAccountService payAccountService;
    @Resource
    private PinganLoanFinanceApplyService pinganLoanFinanceApplyService;
    @Resource
    private PayResultOrderService payResultOrderService;

    public void withdraw2(WithdrawAccountDto withdrawAccountDto) throws PaymentException {
        log.info("PinganAdapter#withdraw2 param={}", JSONUtils.toJSON(withdrawAccountDto));
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("TranWebName", ApolloUtil.getProperty("pingan.mrchCode"));
        requestParam.put("SubAcctNo", withdrawAccountDto.getPayerAccountNo());
        requestParam.put("TranNetMemberCode", withdrawAccountDto.getUserId());
        requestParam.put("MemberName", withdrawAccountDto.getPayerName());
        requestParam.put("TakeCashAcctNo", withdrawAccountDto.getPayeeAccountNo());
        requestParam.put("OutAmtAcctName", withdrawAccountDto.getPayeeName());
        requestParam.put("Ccy", "RMB");
        requestParam.put("CashAmt", AmountCastUtil.castFen2Yuan(withdrawAccountDto.getAmount()));
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6033);
        log.info("PinganAdapter#withdraw2 result={}", JSONUtils.toJSON(result));
    }

    @Autowired
    private AlarmService alarmService;

    /**
     * 创建预支付单
     */
    @Override
    public PrepayBo prepay(PrepayDto prepayDto) throws PaymentException {
        return prePayOrderService.prepayOrderByPrepayDto(prepayDto);
    }
    /**
     * 冻结/解冻
     */
    @Override
    public FrozenBo frozenBalance(FrozenDto frozenDto) throws PaymentException {
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(frozenDto.getAccountId());

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        /**
         * 1：冻结（会员→担保）
         * 2：解冻（担保→会员）
         * 4：见证+收单的冻结资金解冻
         * 5: 可提现冻结（会员→担保）
         * 6: 可提现解冻（担保→会员）
         * 7: 在途充值解冻（担保→会员）
         */
        requestParam.put("FunctionFlag", frozenDto.getFunctionFlag());
        //监管账户的账号
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        //见证子账户的账号
        requestParam.put("SubAcctNo", thirdUserInfo.getThirdUserId());
        //TranNetMemberCode
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));
//            requestParam.put("TranNetMemberCode", param.getUserId());
        //订单的消费金额
        requestParam.put("ConsumeAmt", frozenDto.getAmount());
        //交易金额
        requestParam.put("TranAmt", frozenDto.getAmount());
        //交易手续费
        requestParam.put("TranCommission", "0.00");
        //币种
        requestParam.put("Ccy", "RMB");
        //订单号
        requestParam.put("OrderNo", frozenDto.getOrderNo());
        //订单内容
        requestParam.put("OrderContent", "");
        //备注
        requestParam.put("Remark", "");
        //保留域
        requestParam.put("ReservedMsg", "");
        requestParam.put("CnsmrSeqNo", frozenDto.getBizChannelOrderId());
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6007);
        log.info("frozenBalance response:{}",JSONObject.toJSONString(result));

        // 3.返回响应
        return FrozenBo.build(result.getResultString("FrontSeqNo"));
    }
    /**
     * 查询卡绑定状态
     * @param userId
     * @throws PaymentException
     */
    public List<MerchantCardBindDto> queryBindCardState(String userId) throws PaymentException {
        UserInfoVo userInfoVo = userInfoService2.queryUserInfo(userId);

        if(userInfoVo == null) {
            return Lists.newArrayList();
        }

        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(userId);

        if(thirdUserInfo == null) {
            return Lists.newArrayList();
        }
        // 查询账户信息
        WithdrawAccountDto withdrawAccountDto = paramAssemble.getWithdrawAccountInfo(thirdUserInfo.getUserId());
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        //查询标志：1：全部会员  2：单个会员
        requestParam.put("QueryFlag", "2");
        //监管账户的账号
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        //见证子账户的账号
        requestParam.put("SubAcctNo", withdrawAccountDto.getPayerAccountNo());
        requestParam.put("PageNum", "1");
        //保留域
        requestParam.put("ReservedMsg", "");

        // 2.发送请求
        final PinganResponse response = invoke(requestParam, PinganMethod.KFEJZB6098);
        log.info("queryBindCardState response:{}",JSONObject.toJSONString(response));
        if (!response.success()) {
            return Lists.newArrayList();
        }
        List<MerchantCardBindDto> list = new ArrayList<>();
        Map<String, Object> data = response.getData();
        try {
            if (MapUtils.isNotEmpty(data)) {
                final JSONArray itemArray = JSONArray.parseArray(JSONObject.toJSONString(data.get("TranItemArray")));

                if (itemArray != null) {
                    for (Object obj : itemArray) {
                        final JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(obj));

                        MerchantCardBindDto card = new MerchantCardBindDto();
                        card.setBranchBankCd(jsonObject.getString("CnapsBranchId"));
                        //开户行名称
                        card.setBranchBankName(jsonObject.getString("AcctOpenBranchName"));
                        //账号
                        card.setAccount(jsonObject.getString("MemberAcctNo"));
                        card.setContactPhone(jsonObject.getString("Mobile"));
                        list.add(card);
                    }
                }
            }
        } catch (Exception e) {
            log.error("queryBindCardState",e);
        }

        log.info("queryBindCardState result:{}",JSONObject.toJSONString(list));
        return list;
    }

    /**
     * 查询子账号信息
     * @return
     * @throws PaymentException
     */

    public CustAcctInfoVo queryCustAcctId(String accountId) throws PaymentException {
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(accountId);

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        //监管账户的账号
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        //TranNetMemberCode
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));
//            requestParam.put("TranNetMemberCode", param.getUserId());

        //保留域
        requestParam.put("ReservedMsg", "");
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6037);
        log.info("queryCustAcctId response:{}",JSONObject.toJSONString(result));
        CustAcctInfoVo custAcctInfoVo = new CustAcctInfoVo();
        custAcctInfoVo.setSubAcctNo(result.getResultString("SubAcctNo"));
        custAcctInfoVo.setSubAcctCashBal(result.getResultString("SubAcctCashBal"));
        custAcctInfoVo.setSubAcctAvailBal(result.getResultString("SubAcctAvailBal"));
        custAcctInfoVo.setSubAcctFreezeAmt(result.getResultString("SubAcctFreezeAmt"));


        // 3.返回响应
        return custAcctInfoVo;
    }

    @Override
    public void unBindCard(UnBindCardDto unBindCardDto) throws PaymentException {
        UserInfoVo userInfoVo = userInfoService2.queryUserInfo(unBindCardDto.getUserId());

        if(userInfoVo == null) {
            return;
        }

        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(unBindCardDto.getUserId());

        if(thirdUserInfo == null) {
            return;
        }
        // 查询账户信息
        WithdrawAccountDto withdrawAccountDto = paramAssemble.getWithdrawAccountInfo(thirdUserInfo.getUserId());
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        //1：解绑
        requestParam.put("FunctionFlag", "1");
        //监管账户的账号
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        //TranNetMemberCode
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));
//            requestParam.put("TranNetMemberCode", param.getUserId());
        //见证子账户的账号
//            requestParam.put("SubAcctNo", thirdUserInfo.getThirdUserId());
        requestParam.put("SubAcctNo", withdrawAccountDto.getPayerAccountNo());
        //待解绑的提现账户的账号
        requestParam.put("MemberAcctNo", unBindCardDto.getAccount());
        //保留域
        requestParam.put("ReservedMsg", "");

        // 2.发送请求
        invoke(requestParam, PinganMethod.KFEJZB6065);
    }
    /**
     * 提现申请
     */
    @Override
    public WithdrawalBO withdraw(WithdrawalDto param) throws PaymentException {
        log.info("PinganAdapter#withdraw param={}", JSONUtils.toJSON(param));

        // 查询账户信息
        WithdrawAccountDto withdrawAccountDto = paramAssemble.getWithdrawAccountInfo(param.getUserId());

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("TranWebName", ApolloUtil.getProperty("pingan.mrchCode"));
        requestParam.put("SubAcctNo", withdrawAccountDto.getPayerAccountNo());
        requestParam.put("TranNetMemberCode", param.getUserId());
        requestParam.put("MemberName", withdrawAccountDto.getPayerName());
        requestParam.put("TakeCashAcctNo", withdrawAccountDto.getPayeeAccountNo());
        requestParam.put("OutAmtAcctName", withdrawAccountDto.getPayeeName());
        requestParam.put("Ccy", "RMB");
        requestParam.put("CashAmt", AmountCastUtil.castFen2Yuan(param.getAmount()));
        requestParam.put("Remark", param.getWithdrawalNo());
        requestParam.put("CnsmrSeqNo", param.getWithdrawalNo());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6033);

        // 3.返回响应
        WithdrawalBO withdrawalBO = new WithdrawalBO();
        withdrawalBO.setChannelWithdrawNo(result.getResultString("FrontSeqNo"));
        withdrawalBO.setUserName(withdrawAccountDto.getPayeeName());
        withdrawalBO.setStatus(String.valueOf(WithdrawOrder.STATUS_IN));
        withdrawalBO.setWithdrawalNo(param.getWithdrawalNo());
        return withdrawalBO;
    }

    @Override
    public UserPicBo uploadPic(UserDto userDto) throws PaymentException {
        UserPicBo userPicBo = new UserPicBo();
        userPicBo.setAccountNo(userDto.getAccNo());
        userPicBo.setUser(userDto.getUserId());
        userPicBo.setThirdType(Constants.PINGAN);
        userPicBo.setType(userDto.getAccountType());
        return userPicBo;
    }

    /**
     * 企业开基本户
     */
    @Override
    public SubAppBo createMerchant(UserDto userDto) throws PaymentException {
        log.info("PinganAdapter#createMerchant userDto={}", JSONUtils.toJSON(userDto));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();

        /**
         * 1:开户
         * 2:销户
         * 3:存量实名
         */
        requestParam.put("FunctionFlag", "1");
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(userDto.getUserId()));
        requestParam.put("MemberName", userDto.getName());
        /**
         * 52	组织机构代码证
         * 68	营业执照
         * 73	统一社会信用代码
         * 1	身份证
         * 3	港澳台居民通行证（即回乡证）
         * 4	中国护照
         * 5	台湾居民来往大陆通行证（即台胞证）
         * 19	外国护照
         */
        requestParam.put("MemberGlobalType", "73");
        requestParam.put("MemberGlobalId", userDto.getIdentityInfo().getId());
        requestParam.put("MemberProperty", CustomerTypeEnum.BUYER.getType().equals(userDto.getCustomerType()) ? "00" : "SH");
        /**
         * 法人标志
         * 1 是
         */
        requestParam.put("RepFlag", "1");
        requestParam.put("ReprName", userDto.getContactInfos().get(NumberUtils.INTEGER_ZERO).getContactName());
        requestParam.put("ReprGlobalType", "1");
        requestParam.put("ReprGlobalId", userDto.getContactInfos().get(NumberUtils.INTEGER_ZERO).getContactCertNo());
        requestParam.put("Mobile", userDto.getContactInfos().get(NumberUtils.INTEGER_ZERO).getContactCertMobile());

        requestParam.put("CompanyName", userDto.getName());
        requestParam.put("CompanyGlobalType", "73");
        requestParam.put("CompanyGlobalId", userDto.getIdentityInfo().getId());


        try {
            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6248);

            // 3.保存虚户号
            userService.updateThirdUserIdByUserId(userDto.getUserId(), Constants.PINGAN, result.getResultString("SubAcctNo"));
            userDto.setAccNo(result.getResultString("SubAcctNo"));

            // 4.签约
            createSign(userDto);

            // 5.构建响应参数
            SubAppBo subAppBo = new SubAppBo();
            subAppBo.setState(UserApplyStateEnum.SUCCEEDED);
            subAppBo.setAccountNo(result.getResultString("SubAcctNo"));
            subAppBo.setThirdType(Constants.PINGAN);
            return subAppBo;
        }
        catch(Exception ex) {
            log.error("PinganAdapter#createMerchant error, param=" + JSONUtils.toJSON(userDto) + ", errorMsg=" + ex.getMessage(), ex);
            // 若为买家，且已开户，则不进行异常处理
            if (ex.getMessage().contains("该会员代码已在开户中") && CustomerTypeEnum.BUYER.getType().equals(userDto.getCustomerType())) {
                SubAppBo subAppBo = new SubAppBo();
                subAppBo.setState(UserApplyStateEnum.SUCCEEDED);
                subAppBo.setAccountNo("");
                subAppBo.setThirdType(Constants.PINGAN);
                return subAppBo;
            }
            if(ex instanceof PaymentException) {
                throw new PaymentException(ResultCodeEnum.REGISTER_ACCOUNT_FAIL.getCode(), ((PaymentException) ex).getErrMsg());
            }
            else {
                throw new PaymentException(ResultCodeEnum.REGISTER_ACCOUNT_FAIL);
            }
        }
    }

    /**
     * 企业绑卡
     */
    @Override
    public String merchantCardBind(MerchantCardBindDto merchantCardBindDto) throws PaymentException {
        log.info("PinganAdapter#merchantCardBind merchantCardBindDto={}", JSONUtils.toJSON(merchantCardBindDto));

        try {
            UserInfoVo userInfoVo = userInfoService2.queryUserInfo(merchantCardBindDto.getUserId());

            if(userInfoVo == null) {
                throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
            }

            ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(merchantCardBindDto.getUserId());

            if(thirdUserInfo == null) {
                throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
            }

            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();

            // 基础信息
            requestParam.put("CnsmrSeqNo", merchantCardBindDto.getApplyNo());
            requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
            requestParam.put("SubAcctNo", thirdUserInfo.getThirdUserId());
            requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));

            // 企业信息
            requestParam.put("MemberName", userInfoVo.getBasicInfo().getName());
            requestParam.put("MemberGlobalType", "73");
            requestParam.put("MemberGlobalId", userInfoVo.getBasicInfo().getIdentityInfo().getId());
            requestParam.put("ShopId", userInfoVo.getBasicInfo().getIdentityInfo().getId());
            requestParam.put("ShopName", userInfoVo.getBasicInfo().getName());

            // 法人信息
            /**
             * 1 是
             * 2 否（企业必输）
             */
            requestParam.put("RepFlag", "2");
            requestParam.put("ReprName", merchantCardBindDto.getCorporationName());
            /**
             * 1 身份证
             */
            requestParam.put("ReprGlobalType", "1");
            requestParam.put("ReprGlobalId", merchantCardBindDto.getCorporationIdNo());
            requestParam.put("Mobile", merchantCardBindDto.getCorporationPhone());

            // 银行卡信息
            requestParam.put("MemberAcctNo", merchantCardBindDto.getAccount());
            /**
             * 1 本行
             * 2 他行
             */
            requestParam.put("BankType", "平安银行".equals(merchantCardBindDto.getOpenBank()) ? "1" : "2");
            requestParam.put("AcctOpenBranchName", baseBankService2.convertPinganShortBankName2BankName(merchantCardBindDto.getOpenBank()));
            requestParam.put("CnapsBranchId", merchantCardBindDto.getBranchBankCd());

            // 经办人
            /**
             * 1 是
             * 2 否（企业必输）
             */
            requestParam.put("AgencyClientFlag", "1");
            requestParam.put("AgencyClientName", merchantCardBindDto.getContactName());
            /**
             * 1 身份证
             */
            requestParam.put("AgencyClientGlobalType", "1");
            requestParam.put("AgencyClientGlobalId", merchantCardBindDto.getContactIdCard());
            requestParam.put("AgencyClientMobile", merchantCardBindDto.getContactPhone());

            // 2.发送请求
            PinganResponse response = invoke(requestParam, PinganMethod.KFEJZB6240);
            if (null != response && StringUtils.isNotBlank(response.getResultString("ReservedMsg"))) {
                return response.getResultString("ReservedMsg");
            }
        }
        catch(Exception ex) {
            log.error("PinganAdapter#merchantCardBind error, param=" + JSONUtils.toJSON(merchantCardBindDto) + ", errorMsg=" + ex.getMessage(), ex);

            if(ex instanceof PaymentException) {
                throw new PaymentException(ResultCodeEnum.BANK_CARD_AUDIT_FAIL.getCode(), ((PaymentException) ex).getErrMsg());
            }
            else {
                throw new PaymentException(ResultCodeEnum.BANK_CARD_AUDIT_FAIL);
            }
        }
        return "";
    }

    /**
     * 打款认证结果查询
     */
    @Override
    public PaymentAuthQueryVo paymentAuthQuery(PaymentAuthQueryDto paymentAuthQueryDto) throws PaymentException {
        log.info("PinganAdapter#paymentAuthQuery merchantCardBindDto={}", JSONUtils.toJSON(paymentAuthQueryDto));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("OldTranSeqNo", paymentAuthQueryDto.getApplyNo());
        requestParam.put("TranDate", paymentAuthQueryDto.getApplyDate());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6061);

        // 3.构建响应参数
        return PaymentAuthQueryVo.builder().
                status(result.getResultString("ReturnStatu")).
                message(result.getResultString("ReturnMsg")).
                build();
    }

    /**
     * 打款认证
     */
    @Override
    public PaymentAuthVo paymentAuth(PaymentAuthDto paymentAuthDto) throws PaymentException {
        log.info("PinganAdapter#paymentAuth merchantCardBindDto={}", JSONUtils.toJSON(paymentAuthDto));

        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(paymentAuthDto.getAccountId());

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));
        requestParam.put("SubAcctNo", thirdUserInfo.getThirdUserId());
        requestParam.put("TakeCashAcctNo", paymentAuthDto.getAccountNo());
        requestParam.put("AuthAmt", paymentAuthDto.getAmount());
        requestParam.put("OrderNo", paymentAuthDto.getOrderNo());
        requestParam.put("Ccy", "RMB");

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6241);

        // 3.构建响应参数
        return PaymentAuthVo.builder().
                frontSeqNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 余额查询
     */
    @Override
    public UserBo queryAccount(QueryAccountDto param) throws PaymentException {
        log.info("PinganAdapter#queryAccount param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("SubAcctNo", param.getThirdUserId());

        if(BalanceAccountTypeEnum.FUNCTION_ACCOUNT.getType().equals(param.getBalanceAccountType())) {
            requestParam.put("QueryFlag", "3");
        }
        else {
            requestParam.put("QueryFlag", "2");
        }

        requestParam.put("PageNum", "1");

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6010);

        // 3.解析响应
        QueryAccountBalanceVo accountBalances = QueryAccountBalanceVo.builder().
                totalNum(result.getResultString("TotalNum")).
                resultNum(result.getResultString("ResultNum")).
                endFlag(result.getResultString("EndFlag")).
                accountItems(QueryAccountBalanceVo.parseItems(result.getResultList("AcctArray"))).
                build();

        // 4.返回响应
        return UserBo.builder().
                availableBalance(accountBalances.getSingleAvailableBalance(param.getUserId())).
                withdrawableBalance(accountBalances.getSingleWithdrawBalance(param.getUserId())).
                build();
    }

    /**
     * 查询交易状态（支付平台内部逻用：退款，转账）
     */
    @Override
    public QueryResultBo queryTransactionStatus(QueryTransactionStatusDto param) throws PaymentException {
        log.info("PinganAdapter#queryTransactionStatus param={}", JSONUtils.toJSON(param));

        // 退款
        if(QueryTransactionStatusDto.TRADE_TYPE_REFUND.equals(param.getTradeType())) {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            requestParam.put("TraderNo", ApolloUtil.getProperty("pingan.merchantNo"));
            requestParam.put("ReturnOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + param.getTransactionNo());
            requestParam.put("ReturnOrderSendTime", DateUtil.format(param.getTransactionData(), DateUtil.YYYYMMDDHHMMSS));

            try {
                // 2.发送请求
                PinganResponse result = invoke(requestParam, PinganMethod.REFUND_QUERY);

                // 3.返回响应
                String status = QueryResultBo.convertPingtanRefundStatus(result.getResultString("ReturnOrderStatus"));
                Long successTime = DateUtil.dateToLong(DateUtil.parase(result.getResultString("ReturnSuccessTime"), DateUtil.YYYYMMDDHHMMSS));
                String channelChargeNo = result.getResultString("BankOrderNo");
                Integer amount = Integer.parseInt(result.getResultString("ReturnAmt"));
                return QueryResultBo.build(status, successTime, channelChargeNo, amount);
            }
            catch(PaymentException ex) {
                /**
                 * T6510 订单不存在
                 */
                if("T6510".equals(ex.getErrCode())) {
                    return null;
                }
                else {
                    log.error(ex.getMessage() + ", param=" + JSONUtils.toJSON(param), ex);
                    throw ex;
                }
            }
        }
        // 分账
        else if(QueryTransactionStatusDto.TRADE_TYPE_TRANSFER.equals(param.getTradeType())) {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            requestParam.put("OldThirdSeqNo", param.getTransactionNo());

            try {
                // 2.发送请求
                PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6273);

                // 3.返回响应
                String status = QueryResultBo.convertPingtanSettleStatus(result.getResultString("TranStatus"));
                String channelChargeNo = result.getResultString("OldJzbAcceptSeqNo");
                Integer amount = AmountCastUtil.castYuan2Fen(result.getResultString("TranTotalAmt"));
                return QueryResultBo.build(status, channelChargeNo, amount);
            }
            catch(Exception ex) {
                if(ex instanceof PaymentException) {
                    /**
                     * E00001 查无匹配的交易记录
                     */
                    if("E00001".equals(((PaymentException) ex).getErrCode())) {
                        return null;
                    }
                }
                else {
                    log.error(ex.getMessage() + ", param=" + JSONUtils.toJSON(param), ex);
                    return null;
                }
            }
        }

        throw new UnsupportedOperationException("Unsupported Operation type=" + param.getTradeType());
    }

    /**
     * 查询交易状态（对外提供查询支付，退款，提现状态接口）
     */
    @Override
    public TransactionResultBO queryTransaction(TransactionParam param) throws PaymentException {
        log.info("PinganAdapter#queryTransaction param={}", JSONUtils.toJSON(param));

        if(TransactionTypeEnum.PAY.getCode().equals(param.getTransactionType())) {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            requestParam.put("TraderNo", ApolloUtil.getProperty("pingan.merchantNo"));
            requestParam.put("TraderOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + param.getTradeNo());
            requestParam.put("OrderSendTime", DateUtil.format(param.getTradeTime(), DateUtil.YYYYMMDDHHMMSS));

            try {
                // 2.发送请求
                PinganResponse result = invoke(requestParam, PinganMethod.PAY_QUERY);

                // 3.参数重置
                Map<String, Object> data = result.getData();
                data.put("PaySuccessTime", DateUtil.dateToUnix((String) data.get("PaySuccessTime"), DateUtil.YYYYMMDDHHMMSS));

                PrePayOrder prePayOrder = prePayOrderService.getPrePayOrderByPayNo(param.getTradeNo());

                // 4.响应
                return TransactionResultBO.buildPayByPingan(data, prePayOrder.getExtra());
            }
            catch(PaymentException ex) {
                /**
                 * T6510 原支付订单不存在
                 */
                if("T6510".equals(ex.getErrCode())) {
                    throw new PaymentException(ResultCodeEnum.TRADE_NOT_EXIT_ERROR);
                }
                else {
                    log.error(ex.getErrCode() ,ex.getErrMsg());
                    throw ex;
                }
            }
        }

        throw new UnsupportedOperationException("PinganAdapter#queryTransaction unsupported operation type=" + param.getTransactionType());
    }

    /**
     * 支付
     */
    @Override
    public PayBo pay(PayDto payDto) throws PaymentException {
        log.info("PinganAdapter#pay payDto={}", JSONUtils.toJSON(payDto));

        // 0.从缓存中获取
        PayBo redisPayBo = redisUtil.get(payDto.getPayNo(), PayBo.class);

        if(redisPayBo != null) {
            log.info("PinganAdapter#pay end get from redis appPayBo:{}", JSON.toJSONString(redisPayBo));
            return redisPayBo;
        }

        ThirdUserInfo payee = thirdUserInfoService.getThirdUserInfoByUserId(getPayeeAccountId(payDto.getReceiverId()));

        if(payee == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("TraderNo", ApolloUtil.getProperty("pingan.merchantNo"));
        requestParam.put("TraderOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + payDto.getPayNo());
        if (payDto.getCreateTime() != null) {
            requestParam.put("OrderSendTime", DateUtil.format(payDto.getCreateTime(), DateUtil.YYYYMMDDHHMMSS));
        } else {
            requestParam.put("OrderSendTime", DateUtil.format(DateUtil.now(), DateUtil.YYYYMMDDHHMMSS));
        }

        requestParam.put("OrderName", payDto.getSubject());
        requestParam.put("TranAmt", payDto.getAmount());
        requestParam.put("CallBackNoticeUrl", ApolloUtil.getProperty("pingan.callBack.pay"));

        // 构建响应给业务的参数
        PayBo payBo = new PayBo();

        // 信用支付
        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(payDto.getChannel())) {
            if(StringUtils.isBlank(payDto.getPayerId())) {
                throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
            }

            String payerAccount = payAccountService.getAccountIdByBusiness(BusinessIdTypeEnum.EC.getType(), payDto.getPayerId());

            if(payerAccount == null) {
                throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
            }

            ThirdUserInfo payer = thirdUserInfoService.getThirdUserInfoByUserId(payerAccount);

            if(payer == null) {
                throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
            }

            List<PayFinanceApply> payFinanceApplyList = pinganLoanFinanceApplyService.selectListByBusinessUserIdAndApplyStatus(
                    payDto.getPayerId(), PinganLoanApplyStatusEnum.SIGNED_CONT_SUCCESS.getCode());

            if(payFinanceApplyList == null || payFinanceApplyList.size() != 1) {
                throw new PaymentException(ResultCodeEnum.PINGAN_PAY_FINANCE_APPLY_NOT_SINGLE);
            }

            PayFinanceApply payFinanceApply = payFinanceApplyList.get(0);
            requestParam.put("PayerAcctNo", payFinanceApply.getBecifId());

            if(PayModeEnum.PC.getMode().equals(payDto.getPayMode())) {
                requestParam.put("FrontSkipUrl", payDto.getMap().get("frontSkipUrl"));
                requestParam.put("AccessType", "3");
            }
            else if(PayModeEnum.APP.getMode().equals(payDto.getPayMode())) {
                if(MobileTerminalTypeEnum.ANDROID.getType().equals(payDto.getMobileTerminalType())) {
                    requestParam.put("AndroidCallBackAddress", payDto.getMap().get("androidCallBackAddress"));
                }
                else if(MobileTerminalTypeEnum.iOS.getType().equals(payDto.getMobileTerminalType())) {
                    requestParam.put("IosCallBackAddress", payDto.getMap().get("iosCallBackAddress"));
                }

                requestParam.put("AccessType", "1");
                requestParam.put("ExtendField", payDto.getPayerId());
            }

            requestParam.put("OrderRemark", getPayRemark(payDto, payee, payer));

            PinganResponse result = invoke(requestParam, PinganMethod.CREDIT_PAY);

            if (pinganCodeSwitch) {
                payBo.setPayLoad(result.getResultString("DimensionalCode"));
            } else {
                if(PayModeEnum.PC.getMode().equals(payDto.getPayMode())) {
                    payBo.setPayLoad(result.getResultString("DimensionalCode"));
                }
                else if(PayModeEnum.APP.getMode().equals(payDto.getPayMode())) {
                    payBo.setPayLoad(result.getResultString("Code"));
                }
            }


        }
        // 非信用支付
        else {
            /**
             * WeixinJsapi   微信公众号
             * WeixinProgram 微信小程序
             * AlipayJsapi   支付宝小程序
             */
            final String pinganScenceChannel = getPinganScenceChannel(payDto.getChannel());
            FBankPowerIdEnum powerIdEnum = FBankPowerIdEnum.getPowerId(payDto.getChannel(), payDto.getPayMode(), Constants.PINGAN,pinganScenceChannel);
            requestParam.put("PayModeNo", powerIdEnum.getPowerId());

            if(payDto.getChannel().contains(ChannelEnum.WX.getCode())) {
                requestParam.put("SubTraderPublicId", FBankPowerIdEnum.getAppIdInfo(payDto.getBusinessOrderType(), powerIdEnum).getKey());
                requestParam.put("UserSubId", payDto.getMap().get("openId"));
            }
            else if(payDto.getChannel().contains(ChannelEnum.ALIPAY.getCode())) {
                requestParam.put("UserSubId", payDto.getMap().get("buyerId"));
            }

            requestParam.put("Attach", payDto.getRemark());
            requestParam.put("OrderRemark", getPayRemark(payDto, payee, null));

            PinganResponse result = invoke(requestParam, PinganMethod.PAY);

            if(payDto.getChannel().contains(ChannelEnum.WX.getCode())) {
                payBo.setPayLoad(WxUtil.convertWXPayLoadByPingan(result.getData()));
            }
            else if(payDto.getChannel().contains(ChannelEnum.ALIPAY.getCode())) {
                payBo.setPayLoad(result.getResultString("ChannelOrderNo"));
            }
        }

        payBo.setPayNO(payDto.getPayNo());

        // 4.设置缓存
        redisUtil.set(payDto.getPayNo(), payBo, PayTimeExpireUtil.handleTimeExpire(payDto.getChannel(), payDto.getPayMode(), payDto.getTimeExpire()));

        // 5.返回响应
        return payBo;
    }
    private String getPinganScenceChannel(String channel) {
        if (StringUtils.isEmpty(channel)) {
            return "";
        }
        if(channel.contains(ChannelEnum.WX.getCode())) {
            return pinganScenceMap.get(ChannelEnum.WX.getCode());
        } else if(channel.contains(ChannelEnum.ALIPAY.getCode())) {
            return pinganScenceMap.get(ChannelEnum.ALIPAY.getCode());
        }
        return "";
    }

    /**
     * 平安贷退款
     * 异步交易、结果确认 + 重试
     */
    private RefundBO refundCreditPayV2(RefundDto refundDto) throws PaymentException {
        log.info("PinganAdapter refundCreditPayV2 start refundDto:{}", JSON.toJSONString(refundDto));
        // 获取支付账户id
        String payerAccountId = getPayerAccountId(refundDto);

        String redisLockKey = RedisUtil.PREFIX_PINGAN_CREDIT_PAY_REFUND + payerAccountId;

        try {
            acquireRedisLock(redisLockKey);

            // 根据业务类型处理退款
            handleRefundByBusinessType(refundDto, payerAccountId);

            // 云收款冻结退款
            RefundBO refundBO = refund0(refundDto);
            log.info("PinganAdapter#refundCreditPayV2 finish refundNo:{}", refundDto.getRefundNo());
            return refundBO;
        } finally {
            // 释放买家提现锁
            redisUtil.del(RedisUtil.D_AMOUNT_LOCK + payerAccountId);
            // 释放锁
            releaseRedisLock(redisLockKey);
        }
    }

    private void handleRefundByBusinessType(RefundDto refundDto, String payerAccountId) {
        if (!isBigShop(refundDto.getReceiverId()) && refundDto.getBusinessOrderType().contains(BusinessIdTypeEnum.EC_SELF_SUPPORT.getType())) {
            // 自营
            ecSelfHandlerV2(refundDto, payerAccountId);
        } else if (BusinessIdTypeEnum.EC_POP.getType().equals(refundDto.getBusinessOrderType())) {
            // pop
            ecPopHandlerV2(refundDto, payerAccountId);
        }
    }

    private void acquireRedisLock(String redisLockKey) throws PaymentException {
        if (!redisUtil.setNx(redisLockKey)) {
            throw new PaymentException(ResultCodeEnum.REFUND_BUSY);
        }
    }

    private void releaseRedisLock(String redisLockKey) {
        redisUtil.del(redisLockKey);
    }

    private String getPayerAccountId(RefundDto refundDto) throws PaymentException {
        String payerAccountId = payAccountService.getAccountIdByBusiness(BusinessIdTypeEnum.EC.getType(), refundDto.getPayer());
        if (StringUtils.isBlank(payerAccountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }
        return payerAccountId;
    }

    /**
     * 会员间交易
     */
    @SneakyThrows
    public void asyncAccountTransferAndPoll(AccountTransferDto accountTransferDto) {
        log.info("###异步会员间交易 asyncAccountTransferAndPoll param {}", JSON.toJSONString(accountTransferDto));

        try {
            SeparateOrderAsyncTradeDto asyncTradeDto = separateOrderService2.accountTransferV3(accountTransferDto);
            SeparateOrder separateOrder = asyncTradeDto.getSeparateOrder();

            // 交易成功
            if (asyncTradeDto.getTradeStatus() == SeparateOrderAsyncTradeStatus.FINISHED) {
                log.info("###异步会员间交易 asyncAccountTransferAndPoll 交易成功 {}-{}", separateOrder.getSeparateNo(), accountTransferDto.getBusinessOrderNo());
                return;
            }

            // 轮询渠道交易状态
            String tradeStatus = "";  // 0-处理中  1-处理成功  2-处理失败
            for (int i = 1; i <= asyncAccountTransferAndPollMaxCounts; i++) {
                if (SeparateOrderAsyncTradeStatus.finalStatus().contains(tradeStatus)) {
                    break;
                }

                sleep(i * 1000L);

                QueryTradeStatusVo queryResult = queryTradeStatus(fillQueryTradeStatusDto(accountTransferDto.getBusinessIdType(), separateOrder));
                if (queryResult != null) {
                    tradeStatus = queryResult.getStatus();
                    if (QueryResultBo.FINISHED.equals(queryResult.getStatus())) {
                        separateOrder.setStatus(SeparateOrder.STATUS_FINISH);
                        separateOrder.setSettleNo(queryResult.getBankTradeNo());
                        separateOrder.setUtime(DateUtil.now());
                        separateOrderService.update(separateOrder);
                    }
                }
            }

            log.info("###异步会员间交易 asyncAccountTransferAndPoll {}-{} 查询渠道交易状态tradeStatus:{}", separateOrder.getSeparateNo(), accountTransferDto.getBusinessOrderNo(), tradeStatus);

            if (!SeparateOrder.STATUS_FINISH.equals(separateOrder.getStatus())) {
                throw new RetryException("异步会员间交易失败, separateNo:" + separateOrder.getSeparateNo() + ", tradeOrderNo:" + accountTransferDto.getBusinessOrderNo() +
                        ", 交易状态:" + tradeStatus);
            }

        } catch (Exception e) {
            log.error("###异步会员间交易异常 asyncAccountTransferAndPoll error, {}", accountTransferDto.getBusinessOrderNo(), e);
            if (e instanceof RetryException) {
                throw e;
            }

            // 可重试异常
            if (shouldRetryEx(e)) {
                throw new RetryException("异步会员间交易异常, " + "tradeOrderNo:" + accountTransferDto.getBusinessOrderNo());
            }

            throw e;
        }
    }

    /**
     * 冻结解冻
     */
    @SneakyThrows
    public void asyncUnfreezeMembershipAndPoll(AccountTransferDto accountTransferDto) {
        log.info("###异步冻结解冻 asyncUnfreezeMembershipAndPoll start accountTransferDto:{}", JSON.toJSONString(accountTransferDto));

        try {
            SeparateOrderAsyncTradeDto asyncTradeDto = separateOrderService2.membershipTrancheFreezeV2(accountTransferDto);
            SeparateOrder separateOrder = asyncTradeDto.getSeparateOrder();

            // 交易成功
            if (asyncTradeDto.getTradeStatus() == SeparateOrderAsyncTradeStatus.FINISHED) {
                log.info("###异步冻结解冻 asyncUnfreezeMembershipAndPoll 交易成功 {}-{}", separateOrder.getSeparateNo(), accountTransferDto.getBusinessOrderNo());
                return;
            }

            // 轮询渠道交易状态
            String tradeStatus = "";  // 0-处理中  1-处理成功  2-处理失败
            for (int i = 1; i <= asyncUnfreezeMembershipAndPollMaxCounts; i++) {
                if (SeparateOrderAsyncTradeStatus.finalStatus().contains(tradeStatus)) {
                    break;
                }

                sleep(i * 1000L);

                QueryTradeStatusVo queryResult = queryTradeStatus(fillQueryTradeStatusDto(accountTransferDto.getBusinessIdType(), separateOrder));
                if (queryResult != null) {
                    tradeStatus = queryResult.getStatus();
                    if (QueryResultBo.FINISHED.equals(queryResult.getStatus())) {
                        separateOrder.setStatus(SeparateOrder.STATUS_FINISH);
                        separateOrder.setSettleNo(queryResult.getBankTradeNo());
                        separateOrder.setUtime(DateUtil.now());
                        separateOrderService.update(separateOrder);
                    }
                }
            }

            log.info("###异步冻结解冻 asyncUnfreezeMembershipAndPoll {}-{} 查询渠道交易状态tradeStatus:{}", separateOrder.getSeparateNo(), accountTransferDto.getBusinessOrderNo(), tradeStatus);

            if (!SeparateOrder.STATUS_FINISH.equals(separateOrder.getStatus())) {
                throw new RetryException("异步冻结解冻失败, separateNo:" + separateOrder.getSeparateNo() + ", tradeOrderNo:" + accountTransferDto.getBusinessOrderNo() +
                        ", 交易状态:" + tradeStatus);
            }
        } catch (Exception e) {
            log.error("###异步冻结解冻异常 asyncUnfreezeMembershipAndPoll error, {}", accountTransferDto.getBusinessOrderNo(), e);
            if (e instanceof RetryException) {
                throw e;
            }
            // 可重试异常
            if (shouldRetryEx(e)) {
                throw new RetryException("异步冻结解冻异常, " + "tradeOrderNo:" + accountTransferDto.getBusinessOrderNo());
            }
            throw e;
        }
    }

    private QueryTradeStatusDto fillQueryTradeStatusDto(String businessIdType, SeparateOrder separateOrder) {
        return QueryTradeStatusDto.builder().
                businessIdType(businessIdType).
                accountId(separateOrder.getCustNoIn()).
                tradeType(QueryTradeStatusDto.TRANSFER).
                tradeNo(separateOrder.getSeparateNo()).
                tradeDate(separateOrder.getCtime()).
                build();
    }

    // 会员间交易退款
    @SneakyThrows
    public void asyncTransferRefundAndPoll(AccountTransferRefundDto accountTransferRefundDto) {
        log.info("###异步会员间交易退款 asyncTransferRefundAndPoll param {}", JSON.toJSONString(accountTransferRefundDto));

        try {
            SeparateOrderAsyncTradeDto asyncTradeDto = separateOrderService2.accountTransferRefundSupportPartV2(accountTransferRefundDto);
            SeparateOrder separateOrder = asyncTradeDto.getSeparateOrder();

            // 交易成功
            if (asyncTradeDto.getTradeStatus() == SeparateOrderAsyncTradeStatus.FINISHED) {
                log.info("###异步会员间交易退款 asyncTransferRefundAndPoll 交易成功 {}-{}", separateOrder.getSeparateNo(), accountTransferRefundDto.getBusinessRefundNo());
                return;
            }

            // 轮询渠道交易状态
            String tradeStatus = "";  // 0-处理中  1-处理成功  2-处理失败
            for (int i = 1; i <= asyncTransferRefundAndPollMaxCounts; i++) {
                if (SeparateOrderAsyncTradeStatus.finalStatus().contains(tradeStatus)) {
                    break;
                }

                sleep(i * 1000L);

                // 查询交易结果
                QueryTradeStatusVo queryResult = queryTradeStatus(fillQueryTradeStatusDto(accountTransferRefundDto.getBusinessIdType(), separateOrder));
                if (queryResult != null) {
                    tradeStatus = queryResult.getStatus();
                    if (QueryResultBo.FINISHED.equals(queryResult.getStatus())) {
                        separateOrder.setStatus(SeparateOrder.STATUS_FINISH);
                        separateOrder.setSettleNo(queryResult.getBankTradeNo());
                        separateOrder.setUtime(DateUtil.now());
                        separateOrderService.update(separateOrder);
                    }
                }
            }

            log.info("###异步会员间交易退款 asyncTransferRefundAndPoll {}-{} 查询渠道交易状态tradeStatus:{}", separateOrder.getSeparateNo(), accountTransferRefundDto.getBusinessRefundNo(), tradeStatus);

            if (!SeparateOrder.STATUS_FINISH.equals(separateOrder.getStatus())) {
                throw new RetryException("异步会员间交易退款失败, separateNo:" + separateOrder.getSeparateNo() + ", tradeOrderNo:" + accountTransferRefundDto.getBusinessRefundNo() +
                        ", 交易状态:" + tradeStatus);
            }
        } catch (Exception e) {
            log.error("###异步会员间交易退款异常 asyncTransferRefundAndPoll error, {}-{}", accountTransferRefundDto.getBusinessOrderNo(), accountTransferRefundDto.getBusinessRefundNo(), e);
            if (e instanceof RetryException) {
                throw e;
            }

            // 可重试异常
            if (shouldRetryEx(e)) {
                throw new RetryException("异步会员间交易退款异常, " + "tradeOrderNo:" + accountTransferRefundDto.getBusinessRefundNo());
            }

            throw e;
        }
    }

    // 分账
    @SneakyThrows
    private void asyncSettlementAndPoll(SettleOrderDto settleOrderDto) {
        log.info("###异步分账 asyncSettlementAndPoll start settleOrderDto:{}", JSON.toJSONString(settleOrderDto));

        try {
            SeparateOrderAsyncTradeDto separateOrderAsyncTradeDto = separateOrderService2.settleV2(settleOrderDto);
            SeparateOrder separateOrder = separateOrderAsyncTradeDto.getSeparateOrder();

            // 交易成功
            if (separateOrderAsyncTradeDto.getTradeStatus() == SeparateOrderAsyncTradeStatus.FINISHED) {
                log.info("###异步分账 asyncSettlementAndPoll 交易成功 {}-{}", separateOrder.getSeparateNo(), settleOrderDto.getOrderNo());
                return;
            }

            // 轮询渠道交易状态
            String tradeStatus = "";  // 0-处理中  1-处理成功  2-处理失败
            for (int i = 1; i <= asyncSettlementAndPollMaxCounts; i++) {
                if (SeparateOrderAsyncTradeStatus.finalStatus().contains(tradeStatus)) {
                    break;
                }

                sleep(i * 1000L);

                QueryResultBo queryResultBo = queryTransactionStatus(fillQueryTransactionStatusDto(settleOrderDto.getBusinessIdType(), separateOrder));
                if (queryResultBo != null) {
                    tradeStatus = queryResultBo.getStatus();
                    if (QueryResultBo.FINISHED.equals(queryResultBo.getStatus())) {
                        separateOrder.setStatus(SeparateOrder.STATUS_FINISH);
                        separateOrder.setSettleNo(queryResultBo.getChannelChargeNo());
                        separateOrder.setUtime(DateUtil.now());
                        separateOrderService.update(separateOrder);
                    }
                }
            }

            log.info("###异步分账 asyncSettlementAndPoll {}-{} 查询渠道交易状态tradeStatus:{}", separateOrder.getSeparateNo(), settleOrderDto.getOrderNo(), tradeStatus);

            if (!SeparateOrder.STATUS_FINISH.equals(separateOrder.getStatus())) {
                throw new RetryException("异步分账失败, separateNo:" + separateOrder.getSeparateNo() + ", tradeOrderNo:" + settleOrderDto.getOrderNo() +
                        ", 交易状态:" + tradeStatus);
            }

        } catch (Exception e) {
            log.error("###异步分账异常 asyncSettlementAndPoll error, {}", settleOrderDto.getOrderNo(), e);

            if (e instanceof RetryException) {
                throw e;
            }

            // 可重试异常
            if (shouldRetryEx(e)) {
                throw new RetryException("异步分账异常, " + "tradeOrderNo:" + settleOrderDto.getOrderNo());
            }

            throw e;
        }
    }

    private boolean shouldRetryEx(Exception e) {
        boolean shouldRetry = false;
        if (e instanceof PaymentException) {
            PaymentException px = (PaymentException) e;
            if (!Objects.equals(px.getErrCode(), ResultCodeEnum.PARAM_ERROR.getCode())) {
                shouldRetry = true;
            }

            // 扩展

        }
        return shouldRetry;
    }

    // 分账撤销
    @SneakyThrows
    private void asyncCancelSettlementAndPoll(SettleCancelDto settleCancelDto) {
        log.info("###异步分账撤销 asyncCancelSettlementAndPoll start param:{}", JSON.toJSONString(settleCancelDto));

        try {
            SeparateOrderAsyncTradeDto asyncTradeDto = separateOrderService2.settleCancelSupportPartV2(settleCancelDto);
            SeparateOrder separateOrder = asyncTradeDto.getSeparateOrder();

            if (asyncTradeDto.getTradeStatus() == SeparateOrderAsyncTradeStatus.FINISHED) {
                log.info("###异步分账撤销 asyncCancelSettlementAndPoll 交易成功 {}-{}", separateOrder.getSeparateNo(), settleCancelDto.getRefundOrderNo());
                return;
            }

            // 轮询渠道交易状态
            String tradeStatus = "";  // 0-处理中  1-处理成功  2-处理失败
            for (int i = 1; i <= asyncCancelSettlementAndPollMaxCounts; i++) {
                if (SeparateOrderAsyncTradeStatus.finalStatus().contains(tradeStatus)) {
                    break;
                }

                sleep(i * 1000L);

                QueryResultBo queryResultBo = queryTransactionStatus(fillQueryTransactionStatusDto(settleCancelDto.getBusinessIdType(), separateOrder));
                if (queryResultBo != null) {
                    tradeStatus = queryResultBo.getStatus();
                    if (QueryResultBo.FINISHED.equals(queryResultBo.getStatus())) {
                        separateOrder.setStatus(SeparateOrder.STATUS_FINISH);
                        separateOrder.setSettleNo(queryResultBo.getChannelChargeNo());
                        separateOrder.setUtime(DateUtil.now());
                        separateOrderService.update(separateOrder);
                    }
                }
            }

            log.info("###异步分账撤销 asyncCancelSettlementAndPoll {}-{} 查询渠道交易状态tradeStatus:{}", separateOrder.getSeparateNo(), settleCancelDto.getRefundOrderNo(), tradeStatus);

            if (!SeparateOrder.STATUS_FINISH.equals(separateOrder.getStatus())) {
                throw new RetryException("异步分账撤销失败, separateNo:" + separateOrder.getSeparateNo() + ", tradeOrderNo:" + settleCancelDto.getRefundOrderNo() +
                        ", 交易状态:" + tradeStatus);
            }

        } catch (Exception e) {
            log.error("###异步分账撤销异常 asyncCancelSettlementAndPoll error, {}-{}", settleCancelDto.getOrigOrderNo(), settleCancelDto.getRefundOrderNo(), e);

            if (e instanceof RetryException) {
                throw e;
            }

            // 可重试异常
            if (shouldRetryEx(e)) {
                throw new RetryException("异步分账撤销异常, " + "tradeOrderNo:" + settleCancelDto.getRefundOrderNo() + ", oriSeparateNo:" + settleCancelDto.getOrigOrderNo());
            }

            throw e;
        }
    }

    private QueryTransactionStatusDto fillQueryTransactionStatusDto(String businessIdType, SeparateOrder separateOrder) {
        return QueryTransactionStatusDto.build(
                businessIdType,
                separateOrder.getSeparateNo(),
                QueryTransactionStatusDto.TRADE_TYPE_TRANSFER,
                separateOrder.getCustNoIn(),
                separateOrder.getCtime());
    }

    /**
     * 退款
     */
    @Override
    public RefundBO refund(RefundDto refundDto) throws PaymentException {
        log.info("PinganAdapter#refund refundDto={}", JSONUtils.toJSON(refundDto));

        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(refundDto.getChannel())) {
            return refundCreditPay(refundDto);
        }
        else {
            return refundGeneralPay(refundDto);
        }
    }

    /**
     * 非信用支付退款
     */
    private RefundBO refundGeneralPay(RefundDto refundDto) throws PaymentException {
        String key = null;
        String withdrawKey = null;

        try {
            /**
             * 冻结支付/充值模式需要判断垫资
             * 非大商户、非自营，则走冻结充值撤销，冻结充值撤销，需要判断垫资
             */
            if(!isBigShop(refundDto.getReceiverId()) && BusinessOrderTypeEnum.EC_POP.getType().equals(refundDto.getBusinessOrderType())) {
                // 1.查询冻结余额
                OrderAmountInfoVo orderAmountInfoVo = queryOrderAmountInfo(OrderAmountInfoDto.builder().payNo(refundDto.getPayNo()).build());
                log.info("PinganAdapter#refundGeneralPay refundDto={}, orderAmountInfoVo={}", JSONUtils.toJSON(refundDto), JSONUtils.toJSON(orderAmountInfoVo));

                // 1.正向分账校验：正向分账存在 && 状态为已完成
                List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo());
                boolean isSeparate = !CollectionUtils.isEmpty(separateOrders) && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(NumberUtils.INTEGER_ZERO).getStatus());

                // 冻结金额(平台未分账) < 退款金额
                if(isSeparate) {
                    key = RedisUtil.PREFIX_REFUND + refundDto.getReceiverId() + "_" + refundDto.getRefundNo();

                    if(!redisUtil.setNx(key)) {
                        if (StringUtils.isNotBlank(refundDto.getChannel()) && !refundDto.getChannel().startsWith("pingan_")) {
                            DingTalkMessage msg = DingTalkMessage.builder().type("直连渠道上报平安[充值撤销]").method("PinganAdapter.refundGeneralPay").msg("上报平安充值撤销失败，请重试处理，调用接口：/api/pingan/handleRefund").param(JSONObject.toJSONString(refundDto)).build();
                            alarmService.alarm(msg);
                        }
                        throw new PaymentException(ResultCodeEnum.REFUND_BUSY);
                    }

                    withdrawKey = RedisUtil.D_AMOUNT_LOCK + refundDto.getReceiverId();
                    // 提现锁，可重入无需判断加锁返回值
                    redisUtil.setNx(withdrawKey);

                    // 2.垫资(会员间交易) KFEJZB6034：平安退款账户 -> 商家账户 (退款金额)
                    AccountTransferDto accountTransferDto = AccountTransferDto.builder().
                            transferType(AccountTransferDto.TRANSFER_PAY).
                            businessIdType(refundDto.getBusinessOrderType()).
                            outAccountId(ApolloUtil.getProperty(Constants.EC_PINGAN_REFUND_ACCOUNT_ID)).
                            inAccountId(refundDto.getReceiverId()).
                            amount(refundDto.getAmount()).
                            businessOrderNo(refundDto.getRefundNo()).
                            remark(refundDto.getBusinessRefundNo() + "|" + "退款时-退款垫资").build();
                    separateOrderService2.accountTransfer(accountTransferDto);

                    // 会员间交易是异步，延迟3s等待结果
                    sleep(4000l);

                    // 3.分账撤销 KFEJZB6272：从商家账户 撤销分账 (退款金额)
                    SettleCancelDto settleCancelDto = SettleCancelDto.builder().
                            businessIdType(refundDto.getBusinessOrderType()).
                            accountId(refundDto.getReceiverId()).
                            origOrderNo(refundDto.getPayNo()).
                            origOrderDate(refundDto.getOriTransDate()).
                            totalAmount(refundDto.getAmount()).
                            refundOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_SETTLE_REFUND.name()).
                            remark(refundDto.getBusinessOrderNo()).build();
                    separateOrderService2.settleCancelSupportPart(settleCancelDto);

                    // 分账撤销是异步，延迟5s等待结果
                    sleep(5000l);
                }
            }

            RefundBO refundBO = new RefundBO();
            ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(refundDto.getUserId());

            // 4.退款
            // 大商户、自营
            if(isBigShop(refundDto.getReceiverId()) || refundDto.getBusinessOrderType().contains(BusinessIdTypeEnum.EC_SELF_SUPPORT.getType())) {
                if(refundDto.getChannel().contains(Constants.CHANNEL_DIRECT_NAME) || Constants.thirdChannelSubmitPingan(refundDto.getChannel())) {
                    CommonRechargeRefundDto commonRechargeRefundDto = CommonRechargeRefundDto.builder().
                            businessIdType(refundDto.getBusinessOrderType()).
                            accountId(refundDto.getReceiverId()).
                            channel(RechargePayDto.convertChannel(refundDto.getChannel())).
                            origChannelTradeNo(refundDto.getOrigChannelTransactionNo()).
                            tradeAmount(refundDto.getAmount()).
                            businessOrderNo(refundDto.getRefundNo()+"_"+ AmountTypeEnum.RECHARGEREFUND.name()).
                            bizNo(refundDto.getRefundNo()).
                            build();
                    commonRechargeRefundDto.buildDetail(refundDto.getPayNo(), thirdUserInfo.getThirdUserId(), refundDto.getAmount());
                    // 非冻结充值撤销：KFEJZB6217
//                    RechargeRefundVo rechargeRefundVo = commonRechargeRefund(commonRechargeRefundDto);
                    RechargeRefundVo rechargeRefundVo = separateOrderService2.commonRechargeRefund(commonRechargeRefundDto);
                    refundBO.setChannelRefundNo(rechargeRefundVo!=null?rechargeRefundVo.getRechargeChannelOrderNo():"");
                }
                else {
                    // 4.2云收款退款
                    refundBO = refund0(refundDto);
                }
            }
            else {
                if(refundDto.getChannel().contains(Constants.CHANNEL_DIRECT_NAME) || Constants.thirdChannelSubmitPingan(refundDto.getChannel())) {
                    RechargeRefundDto rechargeRefundDto = RechargeRefundDto.builder().
                            businessIdType(refundDto.getBusinessOrderType()).
                            accountId(refundDto.getReceiverId()).
                            channel(RechargePayDto.convertChannel(refundDto.getChannel())).
                            channelMerchantNo(refundDto.getMerchantId()).
                            origTradeDate(refundDto.getOriTransDate()).
                            channelTradeNo(refundDto.getChannelTransactionNo()).
                            tradeAmount(refundDto.getAmount()).
                            businessOrderNo(refundDto.getRefundNo()+"_"+ AmountTypeEnum.UNFREEZE_RECHARGEREFUND.name()).
                            bizNo(refundDto.getRefundNo()).
                            build();
                    rechargeRefundDto.buildDetail(refundDto.getPayNo(), thirdUserInfo.getThirdUserId(), refundDto.getAmount(), refundDto.getBusinessOrderNo());
                    // 冻结充值撤销：KFEJZB6268
                    RechargeRefundVo rechargeRefundVo = separateOrderService2.rechargeRefund(rechargeRefundDto);
//                    RechargeRefundVo rechargeRefundVo = rechargeRefund(rechargeRefundDto);
                    refundBO.setChannelRefundNo(rechargeRefundVo !=null?rechargeRefundVo.getRechargeChannelOrderNo():"");
                }
                else {
                    // 4.2云收款退款
                    refundBO = refund0(refundDto);
                }
            }

            // 释放锁
            if(!StringUtils.isBlank(withdrawKey)) {
                redisUtil.del(withdrawKey);
            }

            log.info("PinganAdapter#refundGeneralPay finish refundBO={}", JSONUtils.toJSON(refundBO));

            return refundBO;
        }
        finally {
            // 释放锁
            if(!StringUtils.isBlank(key)) {
                redisUtil.del(key);
            }
        }
    }

    /**
     * 信用支付结算
     */
    public void settleCreditPay(PayResultOrder resultOrder, String payerAccount) throws PaymentException {
        String payNo = resultOrder.getPayNo();
        // 支付结算成功标记
        String pinganCreditPaySettleSucKey = RedisUtil.PREFIX_PINGAN_CREDIT_PAY_SETTLE_SUCCESS + payNo;
        // 防止重复结算
        String lockKey = RedisUtil.PREFIX_PINGAN_CREDIT_PAY_SETTLE + payerAccount;
        // 防止买家提现冲突
        String withdrawKey = null;

        if (!redisUtil.setNx(lockKey)) {
            log.error("###平安贷结算 settleCreditPay get lock fail, payNo={}", payNo);
            return;
        }

        // 已结算过滤
        if (redisUtil.exists(pinganCreditPaySettleSucKey)) {
            log.info("###平安贷结算已结算过滤, {}", payNo);
            return;
        }

        try {
            // 校验支付单是否已清分
            String payDate = DateUtil.format(resultOrder.getCtime(), DateUtil.DATEFORMATDAY_02);
            QueryChannelClearVo queryChannelClearVo = queryChannelClear(QueryChannelClearDto.builder().
                    businessIdType(resultOrder.getBusinessOrderType()).
                    accountId(resultOrder.getReceiveId()).
                    startDate(payDate).
                    endDate(payDate).
                    build());
            log.info("PinganAdapter#settleCreditPay, queryChannelClearVo={}, payNo={}", JSON.toJSONString(queryChannelClearVo), payNo);
            // 未清分，不结算
            if (!queryChannelClearVo.isClearFinish()) {
                log.error("PinganAdapter#settleCreditPay, queryChannelClearVo.isClearFinish=false, payNo={}", payNo);
                return;
            }

            // 获取订单结算日期
            Date resultOrderCDate;
            try {
                resultOrderCDate = DateUtil.getDayFirstTime(resultOrder.getCtime());
            } catch (Exception e) {
                log.error("PinganAdapter#settleCreditPay getDayFirstTime error. payNo:{}", payNo, e);
                throw new PaymentException(ResultCodeEnum.DEFAULT_ERROR);
            }

            // 查询待结算金额
            OrderAmountInfoVo orderAmountInfoVo = queryOrderAmountInfo(OrderAmountInfoDto.builder().
                    businessIdType(resultOrder.getBusinessOrderType()).
                    accountId(payerAccount).
                    payNo(resultOrder.getPayNo()).
                    build());
            log.info("PinganAdapter#settleCreditPay before settle orderAmountInfoVo={}, payNo:{}", JSON.toJSONString(orderAmountInfoVo), payNo);
            Integer platformUnsettledAmount = AmountCastUtil.castYuan2Fen(orderAmountInfoVo.getPlatformUnsettledAmount());

            // 如果有待结算金额，则进行结算
            if (platformUnsettledAmount > NumberUtils.INTEGER_ZERO) {
                withdrawKey = RedisUtil.D_AMOUNT_LOCK + payerAccount;
                // 提现锁，可重入无需判断加锁返回值
                redisUtil.setNx(withdrawKey);

                // 分账
                SettleOrderDto settleOrderDto = fillSettleOrderDto(resultOrder, payerAccount, resultOrderCDate, platformUnsettledAmount);
                RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncSettlementAndPoll(settleOrderDto), RetryTradeType.PINGAN_CREDIT_SETTLE, pinganCreditSettleAlarm);

                // 会员间交易至卖家可提现
                AccountTransferDto accountTransferDto = fillAccountTransferDto(resultOrder, payerAccount, platformUnsettledAmount);
                RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncAccountTransferAndPoll(accountTransferDto), RetryTradeType.PINGAN_CREDIT_SETTLE, pinganCreditSettleAlarm);

                // 对非自营订单，资金冻结
                if (!resultOrder.getBusinessOrderType().contains(BusinessIdTypeEnum.EC_SELF_SUPPORT.getType())) {
                    AccountTransferDto frozenParam = fillFrozenAccountTransferDto(resultOrder, platformUnsettledAmount);
                    RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncUnfreezeMembershipAndPoll(frozenParam), RetryTradeType.PINGAN_CREDIT_SETTLE, pinganCreditSettleAlarm);
                }
            } else {
                log.info("pinganCreditPaySettle platformUnsettledAmount=0 no need settle payNo={}", payNo);
            }

            log.info("###平安贷结算 ProfitSharingService#pinganCreditPaySettle success, payNo={}", payNo);

            // 存结算成功标记
            redisUtil.set(pinganCreditPaySettleSucKey, Constants.PINGAN_CREDIT_PAY_JOB_OFFSET_DAY * 86400);

        } finally {
            if (withdrawKey != null) {
                redisUtil.del(withdrawKey);
            }
            redisUtil.del(lockKey);
        }
    }

    private AccountTransferDto fillFrozenAccountTransferDto(PayResultOrder resultOrder, Integer platformUnsettledAmount) {
        return AccountTransferDto.builder().
                transferType(FrozenDto.FUCC_FLAG_5).
                businessIdType(resultOrder.getBusinessOrderType()).
                outAccountId(resultOrder.getReceiveId()).
                inAccountId(resultOrder.getReceiveId()).
                amount(platformUnsettledAmount).
                orderNo(resultOrder.getPayNo() + "_" + AmountTypeEnum.CREDIT_FREEZE.name()).
                businessOrderNo(resultOrder.getPayNo() + "_" + AmountTypeEnum.CREDIT_FREEZE.name()).
                bizNo(resultOrder.getPayNo()).
                extra("冻结").build();
    }

    private AccountTransferDto fillAccountTransferDto(PayResultOrder resultOrder, String payerAccount, Integer platformUnsettledAmount) {
        return AccountTransferDto.builder().
                transferType(AccountTransferDto.TRANSFER_PAY).
                businessIdType(resultOrder.getBusinessOrderType()).
                outAccountId(payerAccount).
                inAccountId(resultOrder.getReceiveId()).
                amount(platformUnsettledAmount).
                businessOrderNo(resultOrder.getPayNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS.name()).build();
    }

    private SettleOrderDto fillSettleOrderDto(PayResultOrder resultOrder, String payerAccount,
                                              Date resultOrderCDate, Integer platformUnsettledAmount) {
        return SettleOrderDto.builder().
                businessIdType(resultOrder.getBusinessOrderType()).
                accountId(payerAccount).
                orderNo(resultOrder.getPayNo()).
                orderDate(resultOrderCDate).
                totalAmount(platformUnsettledAmount).
                build();
    }

    public boolean isPopBizOrderType(String businessOrderType) {
        return BusinessIdTypeEnum.EC_POP.getType().equals(businessOrderType);
    }

    public boolean isSelfSupportBizOrderType(String accountId, String businessOrderType) {
        return isBigShop(accountId) ||
                (StringUtils.isNotBlank(businessOrderType) && businessOrderType.contains(BusinessIdTypeEnum.EC_SELF_SUPPORT.getType()));
    }

    /**
     * 信用支付退款
     */
    private RefundBO refundCreditPay(RefundDto refundDto) throws PaymentException {
        if (Boolean.TRUE.equals(refundCreditPayV2Switch) && CollectionUtil.isNotEmpty(refundCreditPayV2GrayRefundNos) && refundCreditPayV2GrayRefundNos.contains(refundDto.getRefundNo())) {
            return refundCreditPayV2(refundDto);
        }

        if (Boolean.TRUE.equals(refundCreditPayV2InterceptSwitch)) {
            interceptRefundNoForGrayCheck(refundDto);
        }

        String payerAccountId = payAccountService.getAccountIdByBusiness(BusinessIdTypeEnum.EC.getType(), refundDto.getPayer());
        if (StringUtils.isBlank(payerAccountId)) {
            throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
        }
        try {
            String key = RedisUtil.PREFIX_PINGAN_CREDIT_PAY_REFUND + payerAccountId;
            if (!redisUtil.setNx(key)) {
                throw new PaymentException(ResultCodeEnum.REFUND_BUSY);
            }
            //自营
            if(!isBigShop(refundDto.getReceiverId()) && refundDto.getBusinessOrderType().contains(BusinessIdTypeEnum.EC_SELF_SUPPORT.getType())) {
                ecSelfHandler(refundDto,payerAccountId);
            } else if (BusinessIdTypeEnum.EC_POP.getType().equals(refundDto.getBusinessOrderType())) {
                ecPopHandler(refundDto,payerAccountId);
            }
            // 云收款冻结退款
            RefundBO refundBO = refund0(refundDto);
            log.info("PinganAdapter#refundCreditPay finish refundNo:{}", refundDto.getRefundNo());
            return refundBO;
        }
        finally {
            // 释放锁
            redisUtil.del(RedisUtil.PREFIX_PINGAN_CREDIT_PAY_REFUND + payerAccountId);
            // 释放提现锁
            redisUtil.del(RedisUtil.D_AMOUNT_LOCK + payerAccountId);
        }
    }

    private void interceptRefundNoForGrayCheck(RefundDto refundDto) {
        if (refundInterceptMaxCount <= 0) {
            return;
        }

        String redisKey = RedisUtil.INTERCEPTED_REFUND_ORDERS;

        // 检查拦截数量
        Long size = redisUtil.sCard(redisKey);
        if (size != null && size >= refundInterceptMaxCount) {
            return;
        }

        // 拦截
        log.warn("interceptRefundNoForGrayCheck refundDto:{}", JSON.toJSONString(refundDto));

        redisUtil.sAdd(redisKey, refundDto.getRefundNo());
        redisUtil.expire(redisKey, RedisUtil.INTERCEPTED_REFUND_ORDERS_EXPIRE, TimeUnit.MINUTES);

        DingTalkMessage msg = DingTalkMessage.builder().type("平安贷退款单拦截").method("PinganAdapter.interceptRefundNoForGrayCheck").msg("平安贷退款单拦截成功，尽快重试！").param("refundNo:" + refundDto.getRefundNo()).build();
        alarmService.alarm(msg);

        throw new RuntimeException("interceptRefundNoForGrayCheck, refundNo: " + refundDto.getRefundNo());
    }

    private void ecSelfHandlerV2(RefundDto refundDto, String payerAccountId) {
        // 正向分账校验
        List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo());
        boolean isSeparate = !CollectionUtils.isEmpty(separateOrders) && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refund ecSelfHandlerV2 payNo:{} isSeparate:{}", refundDto.getPayNo(), isSeparate);

        redisUtil.setNx(RedisUtil.D_AMOUNT_LOCK + payerAccountId);

        if (isSeparate) {
            // 会员间交易退款
            AccountTransferRefundDto accountTransferRefundDto = fillAccountTransferRefundDto(refundDto);
            RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncTransferRefundAndPoll(accountTransferRefundDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);

            // 买家分账撤销
            SettleCancelDto settleCancelDto = fillRefundSettleCancelDto(refundDto, payerAccountId);
            RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncCancelSettlementAndPoll(settleCancelDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);
        }

    }

    @SneakyThrows
    private void ecPopHandlerV2(RefundDto refundDto, String payerAccountId) {
        // Step 1.校验正向分账状态
        List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo());
        boolean isSeparate = !CollectionUtils.isEmpty(separateOrders) && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refund ecPopHandlerV2 refundNo:{}, isSeparate={}", refundDto.getRefundNo(), isSeparate);
        if (!isSeparate) {
            throw new PaymentException(ResultCodeEnum.ORDER_NONE_SETTLE);
        }

        // Step 2.提现锁，可重入无需判断加锁返回值  todo 买家提现要判断此redis锁
        redisUtil.setNx(RedisUtil.D_AMOUNT_LOCK + payerAccountId);

        // Step 3.正向单状态判断
        // 已分润
        List<SeparateOrder> separateOrders2 = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.CREDIT_PU.name());
        boolean isSharing = !CollectionUtils.isEmpty(separateOrders2) && SeparateOrder.STATUS_FINISH.equals(separateOrders2.get(NumberUtils.INTEGER_ZERO).getStatus());
        // 已结算，卖家资金冻结
        List<SeparateOrder> separateOrders3 = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.CREDIT_FREEZE.name());
        boolean isFreeze = !CollectionUtils.isEmpty(separateOrders3) && SeparateOrder.STATUS_FINISH.equals(separateOrders3.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refund ecPopHandlerV2 refundNo:{}, isSharing:{}, isFreeze:{}", refundDto.getRefundNo(), isSharing, isFreeze);

        // Step 4.垫资、解冻
        if (isSharing) {
            // 退款垫资：平台退款专用户——>卖家可提现
            AccountTransferDto accountTransferDto = fillRefundAccountTransferDto(refundDto);
            RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncAccountTransferAndPoll(accountTransferDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);
        } else if (isFreeze) {
            // 退款解冻：卖家冻结——>卖家可提现
            AccountTransferDto accountTransferDto = fillUnfreezeAccountTransferDto(refundDto);
            RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncUnfreezeMembershipAndPoll(accountTransferDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);
        }

        // Step 5.会员间交易退款：卖家可提现——>买家可提现
        AccountTransferRefundDto accountTransferRefundDto = fillAccountTransferRefundDto(refundDto);
        RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncTransferRefundAndPoll(accountTransferRefundDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);

        // Step 6.买家分账撤销：买家可提现——>买家冻结
        SettleCancelDto settleCancelDto = fillRefundSettleCancelDto(refundDto, payerAccountId);
        RetryStrategy.RETRY_2_ATTEMPTS.executeWithRetry(() -> asyncCancelSettlementAndPoll(settleCancelDto), RetryTradeType.PINGAN_CREDIT_REFUND, pinganCreditRefundAlarm);
    }

    private SettleCancelDto fillRefundSettleCancelDto(RefundDto refundDto, String payerAccountId) {
        return SettleCancelDto.builder().
                businessIdType(refundDto.getBusinessOrderType()).
                accountId(payerAccountId).
                origOrderNo(refundDto.getPayNo()).
                origOrderDate(refundDto.getOriTransDate()).
                refundOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_SETTLE_REFUND.name()).
                totalAmount(refundDto.getAmount()).
                remark(refundDto.getBusinessOrderNo())
                .build();
    }

    private AccountTransferRefundDto fillAccountTransferRefundDto(RefundDto refundDto) {
        return AccountTransferRefundDto.builder().
                businessIdType(refundDto.getBusinessOrderType()).
                amount(Math.abs(refundDto.getAmount())).
                businessOrderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS.name()).
                businessRefundNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS_REFUND.name())
                .build();
    }

    private AccountTransferDto fillUnfreezeAccountTransferDto(RefundDto refundDto) {
        return AccountTransferDto.builder().
                transferType(FrozenDto.FUCC_FLAG_6).
                businessIdType(refundDto.getBusinessOrderType()).
                outAccountId(refundDto.getReceiverId()).
                inAccountId(refundDto.getReceiverId()).
                amount(refundDto.getAmount()).
                orderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.CREDIT_FREEZE.name()).
                businessOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.CREDIT_UNFREEZE.name()).
                bizNo(refundDto.getRefundNo()).
                extra("退款解冻").build();
    }

    private AccountTransferDto fillRefundAccountTransferDto(RefundDto refundDto) {
        return AccountTransferDto.builder().
                transferType(AccountTransferDto.TRANSFER_PAY).
                businessIdType(refundDto.getBusinessOrderType()).
                outAccountId(ApolloUtil.getProperty(Constants.EC_PINGAN_REFUND_ACCOUNT_ID)).
                inAccountId(refundDto.getReceiverId()).
                amount(refundDto.getAmount()).
                businessOrderNo(refundDto.getRefundNo()).
                remark(refundDto.getBusinessRefundNo() + "|" + "退款时-退款垫资").build();
    }

    private void ecSelfHandler(RefundDto refundDto,String payerAccountId) throws PaymentException {
        // 1.正向分账校验
        List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo());
        boolean isSeparate = !CollectionUtils.isEmpty(separateOrders) && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refundCreditPay self payNo:{} isSeparate:{}", refundDto.getPayNo(), isSeparate);

        if (isSeparate) {
            // 事务处理失败不允许买家提现，将来做买家提现要判断此redis锁
            // 提现锁，可重入无需判断加锁返回值
            redisUtil.setNx(RedisUtil.D_AMOUNT_LOCK + payerAccountId);

            // 卖家到买家会员间交易退款KFEJZB6164
            separateOrderService2.accountTransferRefundSupportPart(AccountTransferRefundDto.builder().
                    businessIdType(refundDto.getBusinessOrderType()).
                    amount(Math.abs(refundDto.getAmount())).
                    businessOrderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS.name()).
                    businessRefundNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS_REFUND.name()).build());

            // 会员间交易是异步，延迟3s等待结果
            sleep(4500L);

            // 买家分账撤销KFEJZB6272
            SettleCancelDto settleCancelDto = SettleCancelDto.builder().
                    businessIdType(refundDto.getBusinessOrderType()).
                    accountId(payerAccountId).
                    origOrderNo(refundDto.getPayNo()).
                    origOrderDate(refundDto.getOriTransDate()).
                    refundOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_SETTLE_REFUND.name()).
                    totalAmount(refundDto.getAmount()).
                    remark(refundDto.getBusinessOrderNo()).build();
            separateOrderService2.settleCancelSupportPart(settleCancelDto);

            // 分账撤销是异步，延迟5s等待结果
            sleep(5000L);

        }
    }
    private void ecPopHandler(RefundDto refundDto,String payerAccountId) throws PaymentException {
        // 1.正向分账校验
        List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo());
        boolean isSeparate = !CollectionUtils.isEmpty(separateOrders) && SeparateOrder.STATUS_FINISH.equals(separateOrders.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refundCreditPay pop refundNo:{} isSeparate={}, ", refundDto.getRefundNo(), isSeparate);
        if (!isSeparate) {
            throw new PaymentException(ResultCodeEnum.ORDER_NONE_SETTLE);
        }
        // 事务处理失败不允许买家提现，将来做买家提现要判断此redis锁
        // 提现锁，可重入无需判断加锁返回值
        redisUtil.setNx(RedisUtil.D_AMOUNT_LOCK + payerAccountId);

        List<SeparateOrder> separateOrders2 = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo()+ "_" + AmountTypeEnum.CREDIT_PU.name());
        boolean isSharing = !CollectionUtils.isEmpty(separateOrders2) && SeparateOrder.STATUS_FINISH.equals(separateOrders2.get(NumberUtils.INTEGER_ZERO).getStatus());

        List<SeparateOrder> separateOrders3 = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getPayNo()+ "_" + AmountTypeEnum.CREDIT_FREEZE.name());
        boolean isFreeze = !CollectionUtils.isEmpty(separateOrders3) && SeparateOrder.STATUS_FINISH.equals(separateOrders3.get(NumberUtils.INTEGER_ZERO).getStatus());
        log.info("refundCreditPay pop refundNo:{} isSharing:{} isFreeze:{}",refundDto.getRefundNo(),isSharing,isFreeze);

        if (isSharing) {
            // 2.垫资(会员间交易) KFEJZB6034：平安退款账户 -> 商家账户 (退款金额)
            AccountTransferDto accountTransferDto = AccountTransferDto.builder().
                    transferType(AccountTransferDto.TRANSFER_PAY).
                    businessIdType(refundDto.getBusinessOrderType()).
                    outAccountId(ApolloUtil.getProperty(Constants.EC_PINGAN_REFUND_ACCOUNT_ID)).
                    inAccountId(refundDto.getReceiverId()).
                    amount(refundDto.getAmount()).
                    businessOrderNo(refundDto.getRefundNo()).
                    remark(refundDto.getBusinessRefundNo() + "|" + "退款时-退款垫资").build();
            separateOrderService2.accountTransfer(accountTransferDto);
        } else if (isFreeze) {
            //没分润且钱在商家冻结着
            AccountTransferDto frozenParam = AccountTransferDto.builder().
                    transferType(AccountTransferDto.TRANSFER_PAY_T1).
                    businessIdType(refundDto.getBusinessOrderType()).
                    outAccountId(refundDto.getReceiverId()).
                    inAccountId(refundDto.getReceiverId()).
                    amount(refundDto.getAmount()).
                    orderNo(refundDto.getPayNo()+ "_" + AmountTypeEnum.CREDIT_FREEZE.name()).
                    businessOrderNo(refundDto.getRefundNo()+ "_" + AmountTypeEnum.CREDIT_UNFREEZE.name()).
                    bizNo(refundDto.getRefundNo()).
                    extra("退款解冻").build();
            // 2、卖家解冻6007
            separateOrderService2.membershipTrancheFreeze(frozenParam);
        }

        // 会员间交易是异步，延迟3s等待结果
        sleep(3500L);

        // 卖家到买家会员间交易退款KFEJZB6164
        accountTransferRefundSupportPart(refundDto);

        // 会员间交易是异步，延迟3s等待结果
        sleep(3500L);

        // 买家分账撤销KFEJZB6272
        settleCancelSupportPart(refundDto,payerAccountId);

        // 会员间交易是异步，延迟3s等待结果
        sleep(3000L);

    }

    private void accountTransferRefundSupportPart(RefundDto refundDto) throws PaymentException {
        separateOrderService2.accountTransferRefundSupportPart(AccountTransferRefundDto.builder().
                businessIdType(refundDto.getBusinessOrderType()).
                amount(Math.abs(refundDto.getAmount())).
                businessOrderNo(refundDto.getPayNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS.name()).
                businessRefundNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_ACCT_TRANS_REFUND.name()).build());
    }

    private void settleCancelSupportPart(RefundDto refundDto,String payerAccountId) throws PaymentException {
        SettleCancelDto settleCancelDto = SettleCancelDto.builder().
                businessIdType(refundDto.getBusinessOrderType()).
                accountId(payerAccountId).
                origOrderNo(refundDto.getPayNo()).
                origOrderDate(refundDto.getOriTransDate()).
                refundOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_SETTLE_REFUND.name()).
                totalAmount(refundDto.getAmount()).
                remark(refundDto.getBusinessOrderNo()).build();


        int maxRetryCount = 0;
        boolean isRetry = false;
        boolean isSuccess = false;
        do {
            try {
                // 买家分账撤销
                separateOrderService2.settleCancelSupportPart(settleCancelDto);
                isRetry = false;
                maxRetryCount ++;
                isSuccess = true;
                Thread.sleep(1000);
            } catch (Exception e) {
                maxRetryCount ++;
                isRetry = true;
                log.error("settleCancelSupportPart 平安ePay买家分账撤销失败 refundNo:{}",refundDto.getRefundNo());
            }
            final List<SeparateOrder> separateOrders = separateOrderService.querySeparateOrderByBusinessOrderNo(refundDto.getRefundNo() + "_" + AmountTypeEnum.PINGAN_CREDIT_PAY_SETTLE_REFUND.name());
            final long count = separateOrders.stream().filter(x -> "finish".equalsIgnoreCase(x.getStatus())).count();
            if (count > 0) {
                isRetry = false;
                isSuccess = true;
            }
            if (maxRetryCount > 600) {
                //合计10分钟
                isRetry = false;
            }
        } while (isRetry);
        if (!isSuccess) {
            DingTalkMessage msg = DingTalkMessage.builder()
                    .type("pop平安贷退款")
                    .method("")
                    .msg("平安ePay买家分账撤销失败,请尽快处理有资损风险,RetryCount：" + maxRetryCount)
                    .param(JSONObject.toJSONString(settleCancelDto)).build();
            alarmService.alarm(msg);
        }
    }

    private void sleep(long sleepTime) {

        try {
            Thread.sleep(sleepTime);
        } catch(Exception ex) {
            log.error(ex.getMessage() ,ex);
        }
    }
    /**
     * 订单余额查询（查询6007接口冻结的订单余额）
     */
    public OrderAmountInfoVo orderBalanceQuery(OrderAmountInfoDto param) throws PaymentException {
        log.info("PinganAdapter#orderBalanceQuery param={}", JSONUtils.toJSON(param));
        ThirdUserInfo thirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getAccountId());

        if(thirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        //订单号
        requestParam.put("OrderNo", param.getPayNo());
        //会员子账户
        requestParam.put("MemberSubAcctNo", thirdUserInfo.getThirdUserId());
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6274);

        // 3.返回响应
        return OrderAmountInfoVo.builder().
                payNo(result.getResultString("OrderNo")).
                remainAvailAmt(result.getResultString("RemainAvailAmt")).
                frozenTotalAmt(result.getResultString("FrozenTotalAmt")).
                build();
    }
    /**
     * 查询订单金额信息
     */
    @Override
    public OrderAmountInfoVo queryOrderAmountInfo(OrderAmountInfoDto param) throws PaymentException {
        log.info("PinganAdapter#queryOrderAmountInfo param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("OldPayBussOrderNo", param.getPayNo());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6292);

        // 3.返回响应
        return OrderAmountInfoVo.builder().
                payNo(result.getResultString("OrderNo")).
                bankUnsettledAmount(result.getResultString("NoSquareAmt")).
                bankSettledAmount(result.getResultString("SquareAmt")).
                platformUnsettledAmount(result.getResultString("BranchAmt")).
                platformSettledAmount(result.getResultString("AltAmt")).
                build();
    }

    /**
     * 冻结充值
     */
    @Override
    public RechargePayVo rechargePay(RechargePayDto param) throws PaymentException {
        log.info("PinganAdapter#rechargePay param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("PayChannelType", param.getChannel());
        requestParam.put("PayChannelAssignMerNo", param.getChannelMerchantNo());
        requestParam.put("PayOrderDate", DateUtil.format(param.getTradeDate(), DateUtil.DATEFORMATDAY_02));
        requestParam.put("TotalOrderNo", param.getChannelTradeNo());
        requestParam.put("TranTotalAmt", AmountCastUtil.castFen2Yuan(param.getTradeAmount()));
        requestParam.put("OrdersCount", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6267);

        // 3.返回响应
        return RechargePayVo.builder().
                rechargeChannelOrderNo(result.getResultString("JzbAcceptSeqNo")).
                build();
    }

    /**
     * 冻结充值撤销
     */
    @Override
    public RechargeRefundVo rechargeRefund(RechargeRefundDto param) throws PaymentException {
        log.info("PinganAdapter#rechargeRefund param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("PayChannelType", param.getChannel());
        requestParam.put("PayChannelAssignMerNo", param.getChannelMerchantNo());
        requestParam.put("OldPayOrderDate", DateUtil.format(param.getOrigTradeDate(), DateUtil.DATEFORMATDAY_02));
        requestParam.put("TotalOrderNo", param.getChannelTradeNo());
        requestParam.put("TranTotalAmt", AmountCastUtil.castFen2Yuan(param.getTradeAmount()));
        requestParam.put("OrdersCount", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());
        requestParam.put("CnsmrSeqNo", param.getSeparateNo());
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6268);

        // 3.返回响应
        return RechargeRefundVo.builder().
                rechargeChannelOrderNo(result.getResultString("JzbAcceptSeqNo")).
                build();
    }

    /**
     * 非冻结充值
     */
    @Override
    public RechargePayVo commonRechargePay(CommonRechargePayDto param) throws PaymentException {
        log.info("PinganAdapter#commonRechargePay param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("PayChannelType", param.getChannel());
        requestParam.put("PayChannelAssignMerNo", param.getChannelMerchantNo());
        requestParam.put("TotalOrderNo", param.getChannelTradeNo());
        requestParam.put("TranTotalAmt", AmountCastUtil.castFen2Yuan(param.getTradeAmount()));
        requestParam.put("OrdersCount", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6216);

        // 3.返回响应
        return RechargePayVo.builder().
                rechargeChannelOrderNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 非冻结充值撤销
     */
    @Override
    public RechargeRefundVo commonRechargeRefund(CommonRechargeRefundDto param) throws PaymentException {
        log.info("PinganAdapter#commonRechargeRefund param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("OldPayChannelType", param.getChannel());
        requestParam.put("OldTotalOrderNo", param.getOrigChannelTradeNo());
        requestParam.put("TotalRefundAmt", AmountCastUtil.castFen2Yuan(param.getTradeAmount()));
        requestParam.put("RefundOrderNum", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());
        requestParam.put("CnsmrSeqNo", param.getSeparateNo());
        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6217);

        // 3.返回响应
        return RechargeRefundVo.builder().
                rechargeChannelOrderNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 会员间交易
     */
    @Override
    public AccountTransferVo accountTransfer(AccountTransferDto param) throws PaymentException {
        log.info("PinganAdapter#accountTransfer param={}", JSONUtils.toJSON(param));

        if (Boolean.TRUE.equals(accountTransferMock)) {
            return mockAccountTransfer(param);
        }

        // 出金账户
        ThirdUserInfo outThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getOutAccountId());

        if(outThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 入金账户
        ThirdUserInfo inThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getInAccountId());

        if(inThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FunctionFlag", param.getTransferType());
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("OutSubAcctNo", outThirdUserInfo.getThirdUserId());
        requestParam.put("OutMemberCode", outThirdUserInfo.getUserId());
        requestParam.put("InSubAcctNo", inThirdUserInfo.getThirdUserId());
        requestParam.put("InMemberCode", inThirdUserInfo.getUserId());
        requestParam.put("TranAmt", AmountCastUtil.castFen2Yuan(param.getAmount()));
        requestParam.put("TranFee", AmountCastUtil.castFen2Yuan(param.getFee()));
        requestParam.put("TranType", "01");
        requestParam.put("Ccy", "RMB");
        requestParam.put("Remark", param.getRemark());
        requestParam.put("CnsmrSeqNo", param.getTransferNo());

        // X-会员间交易退款用
        if (Objects.equals(AccountTransferDto.TRANSFER_X, param.getTransferType())) {
            requestParam.put("OrderNo", param.getOrderNo());
        }

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6034);

        // 3.返回响应
        return AccountTransferVo.builder().
                bankTransferNo(result.getResultString("FrontSeqNo")).
                build();
    }

    @SneakyThrows
    private AccountTransferVo mockAccountTransfer(AccountTransferDto param) {
        TimeUnit.SECONDS.sleep(1);

        if (Boolean.TRUE.equals(accountTransferMockError)) {
            throw new PaymentException("mockAccountTransfer error");
        }

        return AccountTransferVo.builder()
                .bankTransferNo(leafUtil.getSnowflakeId())
                .build();
    }

    /**
     * 会员间交易退款
     */
    @Override
    public AccountTransferRefundVo accountTransferRefund(AccountTransferRefundDto param) throws PaymentException {
        log.info("PinganAdapter#accountTransferRefund param={}", JSONUtils.toJSON(param));

        // 原出金账户
        ThirdUserInfo origOutThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getOrigOutAccountId());

        if(origOutThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 入金账户
        ThirdUserInfo origInThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getOrigInAccountId());

        if(origInThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FunctionFlag", param.getTransferType());
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("OldTranSeqNo", param.getOrigTransferNo());
        requestParam.put("OldOutSubAcctNo", origOutThirdUserInfo.getThirdUserId());
        requestParam.put("OldOutMemberCode", origOutThirdUserInfo.getUserId());
        requestParam.put("OldInSubAcctNo", origInThirdUserInfo.getThirdUserId());
        requestParam.put("OldInMemberCode", origInThirdUserInfo.getUserId());
        requestParam.put("ReturnAmt", AmountCastUtil.castFen2Yuan(param.getAmount()));
        requestParam.put("ReturnCommission", AmountCastUtil.castFen2Yuan(param.getFee()));
        requestParam.put("Remark", param.getRemark());
        requestParam.put("ReservedMsg", param.getExtend());

        if (Objects.equals(AccountTransferDto.TRANSFER_X, param.getTransferType())) {
            requestParam.put("OldOrderNo", param.getOrigOrderNo());
        }

        if (StringUtils.isNotBlank(param.getRefundTransferNo())) {
            requestParam.put("CnsmrSeqNo", param.getRefundTransferNo());
        }

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6164);

        // 3.返回响应
        return AccountTransferRefundVo.builder().
                bankTransferNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 查询交易状态
     */
    @Override
    public QueryTradeStatusVo queryTradeStatus(QueryTradeStatusDto param) throws PaymentException {
        log.info("PinganAdapter#queryTradeStatus param={}", JSONUtils.toJSON(param));

        if (Boolean.TRUE.equals(queryTradeStatusMock)) {
            return mockQueryTradeStatus(param);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("FunctionFlag", param.getTradeType());
        requestParam.put("TranNetSeqNo", param.getTradeNo());
        requestParam.put("TranDate", DateUtil.format(param.getTradeDate(), DateUtil.DATEFORMATDAY_02));

        try {
            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6110);

            // 3.返回响应
            return QueryTradeStatusVo.builder().
                    tradeType(result.getResultString("BookingFlag")).
                    status(QueryResultBo.convertPingtanTradeStatus(result.getResultString("TranStatus"))).
                    amount(AmountCastUtil.castYuan2Fen(result.getResultString("TranAmt"))).
                    tradeDate(result.getResultString("TranDate") + result.getResultString("TranTime")).
                    outAccountNo(result.getResultString("OutSubAcctNo")).
                    inAccountNo(result.getResultString("InSubAcctNo")).
                    errorMsg(result.getResultString("FailMsg")).
                    bankTradeNo(result.getResultString("OldTranFrontSeqNo")).
                    build();
        }
        catch(PaymentException ex) {
            /**
             * ERR020 无符合条件记录
             */
            if("ERR020".equals(ex.getErrCode())) {
                return null;
            }
            else {
                log.error(ex.getMessage() + ", param=" + JSONUtils.toJSON(param), ex);
                throw ex;
            }
        }
    }

    @SneakyThrows
    private QueryTradeStatusVo mockQueryTradeStatus(QueryTradeStatusDto param) {

        TimeUnit.SECONDS.sleep(2);

        if (Boolean.TRUE.equals(queryTradeStatusMockError)) {
            // error
            return QueryTradeStatusVo.builder()
                    .status(QueryResultBo.FAILED)
                    .bankTradeNo(leafUtil.getSnowflakeId())
                    .build();
        }

        return QueryTradeStatusVo.builder()
                .status(QueryResultBo.FINISHED)
                .bankTradeNo(leafUtil.getSnowflakeId())
                .build();
    }

    /**
     * 分账
     */
    @Override
    public SettleOrderVo settleOrder(SettleOrderDto param) throws PaymentException {
        log.info("PinganAdapter#settleOrder param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("OldPaySubOrderNo", param.getOrderNo());
        requestParam.put("OldPayOrderDate", DateUtil.format(param.getOrderDate(), DateUtil.DATEFORMATDAY_02));
        requestParam.put("OutSubAcctNo", param.getOutAccountNo());
        requestParam.put("TranTotalAmt", AmountCastUtil.castFen2Yuan(param.getTotalAmount()));
        requestParam.put("OrdersCount", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());
        requestParam.put("CnsmrSeqNo", param.getCnsmrSeqNo());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6271);

        // 3.返回响应
        return SettleOrderVo.builder().
                settleChannelOrderNo(result.getResultString("JzbAcceptSeqNo")).
                build();
    }

    /**
     * 分账撤销
     */
    @Override
    public SettleCancelVo settleCancel(SettleCancelDto param) throws PaymentException {
        log.info("PinganAdapter#settleCancel param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("OldPaySubOrderNo", param.getOrigOrderNo());
        requestParam.put("OldPayOrderDate", DateUtil.format(param.getOrigOrderDate(), DateUtil.DATEFORMATDAY_02));
        requestParam.put("OldOutSubAcctNo", param.getOrigOutAccountNo());
        requestParam.put("TranTotalAmt", AmountCastUtil.castFen2Yuan(param.getTotalAmount()));
        requestParam.put("OrdersCount", param.getOrderItems().size());
        requestParam.put("TranItemArray", param.assembleOrderItems());

        if(StringUtils.isNotBlank(param.getRefundTransferNo())) {
            requestParam.put("CnsmrSeqNo", param.getRefundTransferNo());
        }

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6272);

        // 3.返回响应
        return SettleCancelVo.builder().
                settleChannelOrderNo(result.getResultString("JzbAcceptSeqNo")).
                build();
    }

    /**
     * 挂账登记
     */
    @Override
    public RegisterOrderVo registerOrder(RegisterOrderDto param) throws PaymentException {
        log.info("PinganAdapter#registerOrder param={}", JSONUtils.toJSON(param));

        ThirdUserInfo inThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getAccountId());

        if(inThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("SubAcctNo", inThirdUserInfo.getThirdUserId());
        requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(inThirdUserInfo.getUserId()));
        requestParam.put("OrderNo", param.getOrderNo());
        requestParam.put("SuspendAmt", AmountCastUtil.castFen2Yuan(param.getAmount()));
        requestParam.put("TranFee", AmountCastUtil.castFen2Yuan(param.getFee()));
        requestParam.put("Remark", param.getRemark());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6139);

        // 3.返回响应
        return RegisterOrderVo.builder().
                registerChannelOrderNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 挂账登记撤销
     */
    @Override
    public RegisterCancelVo registerCancel(RegisterCancelDto param) throws PaymentException {
        log.info("PinganAdapter#registerCancel param={}", JSONUtils.toJSON(param));

        ThirdUserInfo outThirdUserInfo = thirdUserInfoService.getThirdUserInfoByUserId(param.getAccountId());

        if(outThirdUserInfo == null) {
            throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("SubAcctNo", outThirdUserInfo.getThirdUserId());
        requestParam.put("TranNetMemberCode", outThirdUserInfo.getUserId());
        requestParam.put("OldOrderNo", param.getOrigOrderNo());
        requestParam.put("CancelAmt", AmountCastUtil.castFen2Yuan(param.getAmount()));
        requestParam.put("TranFee", AmountCastUtil.castFen2Yuan(param.getFee()));
        requestParam.put("Remark", param.getRemark());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6140);

        // 3.返回响应
        return RegisterCancelVo.builder().
                registerChannelOrderNo(result.getResultString("FrontSeqNo")).
                build();
    }

    /**
     * 查询银行清算结果
     */
    @Override
    public QueryChannelClearVo queryChannelClear(QueryChannelClearDto param) throws PaymentException {
        log.info("PinganAdapter#registerCancel param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        /**
         * 查询类型
         * 1 全部
         * 2 指定时间段
         */
        requestParam.put("FunctionFlag", param.getQueryType());
        requestParam.put("StartDate", param.getStartDate());
        requestParam.put("EndDate", param.getEndDate());
        requestParam.put("PageNum", "1");

        try {
            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6108);

            // 3.返回响应
            return QueryChannelClearVo.builder().
                    totalNum(result.getResultString("TotalNum")).
                    resultNum(result.getResultString("ResultNum")).
                    endFlag(result.getResultString("EndFlag")).
                    items(QueryChannelClearVo.parseItems(result.getResultList("TranItemArray"))).
                    build();
        }
        catch(PaymentException ex) {
            /**
             * ERR020 没有找到符合条件的记录
             */
            if("ERR020".equals(ex.getErrCode())) {
                return QueryChannelClearVo.builder().build();
            }

            throw ex;
        }
    }

    /**
     * 提现退票查询
     */
    @Override
    public WithdrawRefundVo withdrawRefund(WithdrawRefundDto param) throws PaymentException {
        log.info("PinganAdapter#withdrawRefund param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        /**
         * 1 提现退票
         * 2 小额鉴权退票
         */
        requestParam.put("FunctionFlag", param.getRefundType());
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("StartDate", DateUtil.format(param.getStartDate(), DateUtil.DATEFORMATDAY_02));
        requestParam.put("EndDate", DateUtil.format(param.getEndDate(), DateUtil.DATEFORMATDAY_02));

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6048);

        // 3.返回响应
        return WithdrawRefundVo.builder().
                resultNum(result.getResultString("ResultNum")).
                withdrawRefundItems(WithdrawRefundVo.parseItems(result.getResultList("TranItemArray"))).
                build();
    }

    /**
     * 查询对账文件信息
     */
    @Override
    public QueryReconciliationFileVo downReconciliationFile(QueryReconciliationFileDto param) throws PaymentException {
        log.info("PinganAdapter#queryReconciliationFile param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("FileType", param.getFileType());
        requestParam.put("FileDate", DateUtil.format(param.getFileDate(), DateUtil.DATEFORMATDAY_02));

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6103);

        // 3.解析参数
        QueryReconciliationFileVo fileVo = QueryReconciliationFileVo.builder().
                resultNum(result.getResultString("ResultNum")).
                fileItems(QueryReconciliationFileVo.parseItems(result.getResultList("TranItemArray"))).
                build();

        // 4.下载
        downloadFile(fileVo);

        // 5.返回响应
        return fileVo;
    }

    /**
     * 查询支付凭证信息
     */
    @Override
    public TradeCertificateVo queryTradeCertificate(QueryTradeCertificateDto param) throws PaymentException {
        log.info("PinganAdapter#queryTradeCertificate param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("OldFrontSeqNo", param.getChannelTradeNo());
        requestParam.put("OldTranType", "1");

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6142);

        // 3.返回响应
        return TradeCertificateVo.builder().
                channelTradeNo(result.getResultString("OldFrontSeqNo")).
                checkCode(result.getResultString("DetailCheckCode")).
                build();
    }

    /**
     * 补单
     */
    @Override
    public OrderSupplyVo orderSupply(OrderSupplyDto param) throws PaymentException {
        log.info("PinganAdapter#orderSupply param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("AcquiringChannelType", "YST1");
        requestParam.put("OrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + param.getPayNo());
        requestParam.put("Amt", AmountCastUtil.castFen2Yuan(param.getAmount()));

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6147);

        // 3.返回响应
        return OrderSupplyVo.builder().
                bankOrderNo(result.getResultString("FrontSeqNo")).
                thirdUserId(result.getResultString("SubAcctNo")).
                amount(AmountCastUtil.castYuan2Fen(result.getResultString("Amt"))).
                remark(result.getResultString("Remark")).
                build();
    }

    @Override
    public QueryThirdUserIdVo queryThirdUserId(QueryThirdUserIdDto param) throws PaymentException {
        log.info("PinganAdapter#queryThirdUserId param={}", JSONUtils.toJSON(param));

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        requestParam.put("TranNetMemberCode", param.getAccountId());

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6147);

        // 3.返回响应
        return QueryThirdUserIdVo.builder().thirdUserId(result.getResultString("SubAcctNo")).build();
    }

    /**
     * 开户签约
     */
    private void createSign(UserDto userDto) {
        log.info("PinganAdapter#createSign userDto={}", JSONUtils.toJSON(userDto));

        try {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            requestParam.put("FunctionFlag", "1");
            requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
            requestParam.put("SubAcctNo", userDto.getAccNo());
            requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(userDto.getUserId()));
            requestParam.put("OpClickTime", DateUtil.format(DateUtil.now(), DateUtil.YYYYMMDDHHMMSS));
            requestParam.put("IpAddress", userDto.getOperatorInfo().getOperatorIp());
            requestParam.put("MacAddress", userDto.getOperatorInfo().getMac());
            requestParam.put("SignChannel", "2");

            // 2.发送请求
            invoke(requestParam, PinganMethod.KFEJZB6244);
        }
        catch(Exception ex) {
            log.error("PinganAdapter#createSign error, param=" + JSONUtils.toJSON(userDto) + ", errorMsg=" + ex.getMessage(), ex);
        }
    }

    /**
     * 获取支付备注
     */
    private String getPayRemark(PayDto payDto, ThirdUserInfo payee, ThirdUserInfo payer) {
        List<Map<String, String>> oderList = new ArrayList<>();
        Map<String, String> subOrder = new HashMap<>();
        boolean v2 = false;
        Map<String, Object> remark = new HashMap<>();
        /**
         * 0 冻结支付
         * 1 普通支付
         */
        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(payDto.getChannel())) {
            // 信用支付
            subOrder.put("SubAccNo", payer.getThirdUserId());
            subOrder.put("PayModel", "0");
            //过账方子账户
            remark.put("transferAccNo", payee.getAccountNo());
        }
        else {
            // 非信用支付
            subOrder.put("SubAccNo", payee.getThirdUserId());
            String payModel = !isBigShop(payee.getUserId()) && BusinessOrderTypeEnum.EC_POP.getType().equals(payDto.getBusinessOrderType()) ? "0" : "1";
            subOrder.put("PayModel", payModel);

            // 非冻结支付走2.0接口
            if("1".equals(payModel)) {
                v2 = true;
            }
        }
        subOrder.put("TranFee", "0.00");
        subOrder.put("subamount", AmountCastUtil.castFen2Yuan2(payDto.getAmount()));
        subOrder.put("suborderId", payDto.getPayNo());
        subOrder.put("object", payDto.getSubject());
        oderList.add(subOrder);

        remark.put("SFJOrdertype", "1");
        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(payDto.getChannel())) {
            remark.put("remarktype", "JHS011ZFRZ");
        } else {
            remark.put("remarktype", "JHS0100000");
        }

        remark.put("plantCode", ApolloUtil.getProperty("pingan.mrchCode"));

        if(v2) {
            remark.put("version", "2.0");
        }

        remark.put("oderlist", oderList);

        List<Map<String, Object>> result = new ArrayList<>();
        result.add(remark);

        return JSONUtils.toJSON(result);
    }

    /**
     * 获取退款备注
     */
    private String getRefundRemark(RefundDto refundDto, ThirdUserInfo payee, ThirdUserInfo payer) {
        List<Map<String, String>> oderList = new ArrayList<>();
        Map<String, String> subOrder = new HashMap<>();
        boolean v2 = false;
        Map<String, Object> remark = new HashMap<>();

        /**
         * 0 冻结退
         * 1 普通退
         */
        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(refundDto.getChannel())) {
            // 信用支付
            subOrder.put("SubAccNo", payer.getThirdUserId());
            subOrder.put("refundModel", "0");
            //过账方子账户
            remark.put("transferAccNo", payee.getAccountNo());
        }
        else {
            // 非信用支付
            subOrder.put("SubAccNo", payee.getThirdUserId());
            String refundModel = !isBigShop(payee.getUserId()) && BusinessOrderTypeEnum.EC_POP.getType().equals(refundDto.getBusinessOrderType()) ? "0" : "1";
            subOrder.put("refundModel", refundModel);

            // 非冻结支付走2.0接口
            if("1".equals(refundModel)) {
                v2 = true;
            }
        }

        subOrder.put("RefundTranFee", "0.00");
        subOrder.put("subrefundamt", AmountCastUtil.castFen2Yuan2(refundDto.getAmount()));
        subOrder.put("suborderId", refundDto.getPayNo());

        if(Boolean.TRUE.equals(refundDto.getNeedChangeRefundNo())) {
            subOrder.put("subrefundId", refundDto.getChannelRefundNo());
        }
        else {
            subOrder.put("subrefundId", refundDto.getRefundNo());
        }

        subOrder.put("object", refundDto.getDescription());
        oderList.add(subOrder);

        remark.put("SFJOrdertype", "1");
        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(refundDto.getChannel())) {
            remark.put("remarktype", "JHS011ZFRZ");
        } else {
            remark.put("remarktype", "JHS0100000");
        }
        remark.put("plantCode", ApolloUtil.getProperty("pingan.mrchCode"));

        if(v2) {
            remark.put("version", "2.0");
        }

        remark.put("oderlist", oderList);

        List<Map<String, Object>> result = new ArrayList<>();
        result.add(remark);

        return JSONUtils.toJSON(result);
    }

    /**
     * 退款
     */
    private RefundBO refund0(RefundDto refundDto) throws PaymentException {
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("TraderNo", ApolloUtil.getProperty("pingan.merchantNo"));

        if(Boolean.TRUE.equals(refundDto.getNeedChangeRefundNo())) {
            requestParam.put("ReturnOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + refundDto.getChannelRefundNo());
        }
        else {
            requestParam.put("ReturnOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + refundDto.getRefundNo());
        }

        requestParam.put("ReturnOrderSendTime", DateUtil.format(refundDto.getTransDate(), DateUtil.YYYYMMDDHHMMSS));
        requestParam.put("OldMerOrderNo", Constants.TOTAL_ORDER_NUMBER_PREFIX + refundDto.getPayNo());
        requestParam.put("OldOrderSendTime", DateUtil.format(refundDto.getOriTransDate(), DateUtil.YYYYMMDDHHMMSS));
        requestParam.put("ReturnAmt", refundDto.getAmount());

        ThirdUserInfo payee = thirdUserInfoService.getThirdUserInfoByUserId(getPayeeAccountId(refundDto.getUserId()));

        if(ChannelEnum.PINGAN_CREDIT.getCode().equals(refundDto.getChannel())) {
            // 信用支付
            String payerAccount = payAccountService.getAccountIdByBusiness(BusinessIdTypeEnum.EC.getType(), refundDto.getPayer());

            if(payerAccount == null) {
                throw new PaymentException(ResultCodeEnum.USER_NOT_FOUND);
            }

            ThirdUserInfo payer = thirdUserInfoService.getThirdUserInfoByUserId(payerAccount);

            if(payer == null) {
                throw new PaymentException(ResultCodeEnum.THIRD_USER_ID_NOT_EXIST);
            }

            requestParam.put("RefundRemark", getRefundRemark(refundDto, payee, payer));
        }
        else {
            // 非信用支付
            requestParam.put("RefundRemark", getRefundRemark(refundDto, payee, null));
        }

        // 2.发送请求
        PinganResponse result = invoke(requestParam, PinganMethod.REFUND);

        // 3.返回响应
        RefundBO refundBO = new RefundBO();
        refundBO.setRefundNo(refundDto.getRefundNo());

        if(Boolean.TRUE.equals(refundDto.getNeedChangeRefundNo())) {
            refundBO.setChannelRefundNo(requestParam.getString("ReturnOrderNo"));
        }
        else {
            refundBO.setChannelRefundNo(result.getResultString("BankOrderNo"));
        }

        refundBO.setChannelTransactionNo(result.getResultString("ChannelOrderNo"));
        refundBO.setDate(DateUtil.parase(result.getResultString("CreateDate"), DateUtil.YYYYMMDDHHMMSS));
        refundBO.setStatus(RefundOrder.convertPinganRefundStatus(result.getResultString("ReturnOrderStatus")));
        return refundBO;
    }

    /**
     * 获取收款人信息
     */
    private String getPayeeAccountId(String accountId) {
        if(StringUtils.isBlank(accountId)) {
            return accountId;
        }

        return isBigShop(accountId) ? ApolloUtil.getProperty(Constants.EC_POP_BIG_SHOP_PINGAN_ACCOUNT_ID) : accountId;
    }

    /**
     * 判断是否为大商户
     */
    private boolean isBigShop(String accountId) {
        return Objects.equals(ApolloUtil.getProperty(Constants.POP_DIRECT_PAY_YBM_TECH_USER_ID), accountId) ||
                Objects.equals(ApolloUtil.getProperty(Constants.EC_POP_BIG_SHOP_PINGAN_ACCOUNT_ID), accountId);
    }

    /**
     * 适配换签商户标识
     */
    private String signChangeAccountIdAdapter(String accountId) {
        if(StringUtils.isBlank(accountId)) {
            return accountId;
        }

        PayAccount payAccount = payAccountService.getPayAccountByAccountId(accountId);

        if(payAccount.getBusinessUserId().contains(AccountDto.SUFFIX_SIGN_CHANGE)) {
            PayAccount oldPayAccount = payAccountService.selectByBusiness(payAccount.getBusinessIdType(), payAccount.getBusinessUserId().
                    substring(NumberUtils.INTEGER_ZERO, payAccount.getBusinessUserId().indexOf(AccountDto.SUFFIX_SIGN_CHANGE)));
            return oldPayAccount.getAccountId();
        }
        else {
            return accountId;
        }
    }

    /**
     * 文件下载
     */
    private void downloadFile(QueryReconciliationFileVo param) {
        log.info("PinganAdapter#downloadFile param={}", JSONUtils.toJSON(param));
        List<QueryReconciliationFileItem> fileItems = param.getFileItems();

        if(CollectionUtils.isEmpty(fileItems)) {
            return;
        }

        for(QueryReconciliationFileItem item : fileItems) {
            // 1.组装参数
            FileRequest fileRequest = new FileRequest();
            fileRequest.setFileNo(item.getFilePath());
            fileRequest.setRandomPwd(item.getRandomPassword());
            PicUtil.createFilePath(PicUtil.downloadFilePath);
            fileRequest.setFilePathName(PicUtil.downloadFilePath + item.getFileName());

            // 2.发送请求下载文件
            apiClient.fileDownLoad(fileRequest);

            String realFileName = item.getFileName().substring(NumberUtils.INTEGER_ZERO, item.getFileName().lastIndexOf("."));
            String srcFilePath = PicUtil.downloadFilePath + realFileName + ".enc";
            String zipFilePath = PicUtil.downloadFilePath + realFileName + ".zip";
            byte[] bKey = Base64.decode(item.getRandomPassword().getBytes());

            log.info("PinganAdapter#downloadFile srcFilePath={}", srcFilePath);
            log.info("PinganAdapter#downloadFile zipFilePath={}", zipFilePath);

            try {
                // 3.解密
                SignUtil.decrypt(srcFilePath, zipFilePath, bKey, SignUtil.ALG_DES, SignUtil.KS_DES,  null);

                // 4.解压
                SignUtil.uncompress(zipFilePath, PicUtil.downloadFilePath + realFileName);
                log.info("PinganAdapter#downloadFile realFileName={}", PicUtil.downloadFilePath + realFileName);
            }
            catch(Exception ex) {
                log.error(ex.getMessage() ,ex);
            }
        }
    }

    /**
     * 平安请求
     */
    private PinganResponse invoke(JSONObject param, PinganMethod method) throws PaymentException {
        try {
            // 1.请求头
            if(!param.containsKey("CnsmrSeqNo")) {
                param.put("CnsmrSeqNo", leafUtil.getSnowflakeId());
            }

            if(!PinganMethod.NOT_REQUIRED.equals(method.getMethodCode())) {
                param.put("TxnCode", method.getMethodCode());
                param.put("TxnTime", DateUtil.format(DateUtil.now(), DateUtil.YYYYMMDDHHMMSS) + "000");
                param.put("TxnClientNo", ApolloUtil.getProperty("pingan.clientNo"));
            }

            param.put("MrchCode", ApolloUtil.getProperty("pingan.mrchCode"));

            // 2.请求体
            SdkRequest sdkRequest = new SdkRequest();
            sdkRequest.setInterfaceName("/V1.0/" + method.getMethodName());
            sdkRequest.setBody(param);

            // 3.发送请求
            log.info("PinganAdapter#invoke method:{} request:{}", method.name(), JSONUtils.toJSON(param));
            HttpResult response = apiClient.invoke(sdkRequest);
            log.info("PinganAdapter#invoke method:{}（{}） request:{} response:{}", method.name(), method.getDesc(),
                    JSONUtils.toJSON(param), JSONUtils.toJSON(response != null ? response.getData() : response));

            // 4.通信状态判断
            if(response == null || !response.isSuccess()) {
                throw new PaymentException(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
            }

            // 5.响应
            return PinganResponse.parse(response.getData());
        }
        catch(Exception ex) {
            log.error("PinganAdapter#invoke error, method={}, param={}, errorMsg={}", method.getMethodCode(), JSONUtils.toJSON(param), ex.getMessage(), ex);
            if (PinganMethod.ALARM_METHOD.contains(method.getMethodCode())) {
                DingTalkMessage msg = DingTalkMessage.builder()
                        .type("调用平安接口异常")
                        .method("PinganAdapter.invoke#" + method.getMethodCode())
                        .msg("接口：" + method.getMethodCode() + "(" + method.getDesc() + ")调用失败")
                        .param(param.toJSONString())
                        .build();
                alarmService.alarm(msg);
            }

            if(ex instanceof OpenBankSdkException) {
                throw new PaymentException(((OpenBankSdkException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof OpenBankSdkRunTimeException) {
                throw new PaymentException(((OpenBankSdkRunTimeException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof OpenBankFileNotFoundException) {
                throw new PaymentException(((OpenBankFileNotFoundException) ex).getCode(), ex.getMessage());
            }
            else if(ex instanceof PaymentException) {
                throw ex;
            }

            throw new PaymentException(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
        }
    }

    /**
     * 调用6292 更新平安商户信息
     */
    public void updateAccount(AccountUpdateVo accountUpdateVo, ThirdUserInfo thirdUserInfo) throws PaymentException {
        try {
            // 1.组装请求参数
            JSONObject requestParam = new JSONObject();
            // 子账户账号
            requestParam.put("SubAcctNo", thirdUserInfo.getThirdUserId());
            // 交易网会员代码
            requestParam.put("TranNetMemberCode", signChangeAccountIdAdapter(thirdUserInfo.getUserId()));
            // 客户真实姓名
            requestParam.put("MemberName", accountUpdateVo.getMemberName());
            // 账户为企业时公司名称、法人名称、法人证件类型、法人证件号码必输,且公司名称与会员名称一致
            requestParam.put("CompanyName", accountUpdateVo.getCompanyName());
            // 公司证件类型
            requestParam.put("CompanyGlobalType", accountUpdateVo.getCompanyGlobalType());
            // 公司证件号码
            requestParam.put("CompanyGlobalId", accountUpdateVo.getCompanyGlobalId());
            // 法人名称
            requestParam.put("ReprName", accountUpdateVo.getReprName());
            // 法人证件类型
            requestParam.put("ReprGlobalType", "1");
            // 法人证件号码
            requestParam.put("ReprGlobalId", accountUpdateVo.getReprGlobalId());
            // 保留域
            requestParam.put("ReservedMsg", "");

            // 2.发送请求
            PinganResponse result = invoke(requestParam, PinganMethod.KFEJZB6296);

            log.info("updateAccount company result:{} {}", JSONObject.toJSONString(result), JSON.toJSONString(accountUpdateVo));
        } catch (Exception e) {
            log.error("updateAccount company error:{} {}", JSON.toJSONString(accountUpdateVo), e.getMessage(), e);
            throw e;
        }
    }

    /**
     * 平安响应结构体
     */
    @Getter
    @Setter
    public static class PinganResponse {
        private final static String PINGAN_SUCCESS_CODE = "000000";
        private final static String PINGAN_PAY_SUCCESS_CODE = "T0000";

        private String code;
        private String message;
        private Map<String, Object> extendData;
        private Map<String, Object> data;
        private List<Map<String, Object>> errors;

        /**
         * 是否成功
         */
        public boolean success() {
            if(StringUtils.isBlank(code)) {
                code = getResultString("TxnReturnCode");
            }

            if(StringUtils.isBlank(code)) {
                code = getResultString("ResponseCode");
            }

            return PINGAN_SUCCESS_CODE.equals(code) ||
                    PINGAN_PAY_SUCCESS_CODE.equals(code);
        }

        /**
         * 获取结果（String）
         */
        public String getResultString(String key) {
            if(data == null) {
                return null;
            }

            if(data.get(key) == null) {
                Object innerData = data.get("Data");

                if(innerData != null && innerData instanceof JSONObject) {
                    JSONObject innerDataJsonObject = (JSONObject) innerData;

                    if(innerDataJsonObject.get(key) == null) {
                        return null;
                    }

                    return (String) innerDataJsonObject.get(key);
                }

                return null;
            }

            return (String) data.get(key);
        }

        /**
         * 获取结果（List）
         */
        public List<Map<String, String>> getResultList(String key) {
            return data == null ? null : (List<Map<String, String>>) data.get(key);
        }

        /**
         * 解析响应
         */
        public static PinganResponse parse(String body) throws PaymentException {
            if(StringUtils.isBlank(body)) {
                log.error("PinganResponse parse, body is blank");
                throw new PaymentException(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
            }

            JSONObject param = JSON.parseObject(body);
            PinganResponse response = new PinganResponse();
            response.setCode(param.getString("Code"));
            response.setMessage(param.getString("Message"));
            response.setExtendData(JSONUtils.parse2Map(param.getString("ExtendData"), String.class, Object.class));
            response.setData(param.getJSONObject("Data"));
            response.errors = new ArrayList<>();
            JSONArray errorJsonArray = param.getJSONArray("Errors");

            if(errorJsonArray != null) {
                for(int i = 0; i < errorJsonArray.size(); i++) {
                    response.errors.add(errorJsonArray.getJSONObject(i));
                }
            }

            // 失败直接抛出业务异常
            if(!response.success()) {
                String errorCode = (String) response.extendData.get("TxnReturnCode");
                String errorMessage = (String) response.extendData.get("TxnReturnMsg");

                if(StringUtils.isBlank(errorCode)) {
                    errorCode = response.getResultString("TxnReturnCode");
                    errorMessage = response.getResultString("TxnReturnMsg");
                }

                if(StringUtils.isBlank(errorCode)) {
                    errorCode = "";
                    errorMessage = "";

                    if(response.errors != null) {
                        for (Map<String, Object> error : response.errors) {
                            if(error.get("ErrorCode") != null) {
                                errorCode += error.get("ErrorCode");
                                errorCode += "|";
                            }

                            if(error.get("ErrorMessage") != null) {
                                errorMessage += error.get("ErrorMessage");
                                errorMessage += "|";
                            }
                        }
                    }

                    if(response.extendData != null) {
                        if(response.extendData.get("ResponseCode") != null) {
                            errorCode += response.extendData.get("ResponseCode");
                        }

                        if(response.extendData.get("ResponseMessage") != null) {
                            errorMessage += response.extendData.get("ResponseMessage");
                        }
                    }
                }

                throw new PaymentException(errorCode, errorMessage);
            }

            return response;
        }
    }

    @Override
    public QueryResultBo queryRefundStatus(RefundOrder refundOrder) throws PaymentException {
        PayResultOrder resultOrder = payResultOrderService.selectByPayNoAndChannel(refundOrder.getPayNo(), refundOrder.getChannel());
        // 230713：解决部分退款查询失败：当ChannelRefundNo以T开头，使用ChannelRefundNo查询，否则使用refundNo
        String transactionNo = refundOrder.getRefundNo();
        if(StringUtils.isNotBlank(refundOrder.getChannelRefundNo()) && refundOrder.getChannelRefundNo().startsWith(Constants.TOTAL_ORDER_NUMBER_PREFIX)) {
            transactionNo = refundOrder.getChannelRefundNo().substring(NumberUtils.INTEGER_ONE);
        }
        QueryTransactionStatusDto queryTransactionStatusDto = QueryTransactionStatusDto. build(resultOrder.getBusinessOrderType(), transactionNo, QueryTransactionStatusDto.TRADE_TYPE_REFUND, getPayeeAccountId(refundOrder.getUserId()), refundOrder.getUpdateTime());
        QueryResultBo queryResultBo = queryTransactionStatus(queryTransactionStatusDto);
        queryResultBo.setStatus(QueryResultBo.convertStatus(queryResultBo.getStatus()));
        return queryResultBo;

    }


    /**
     * 实名开同名账户关联关系维护
     * @param thirdUserId
     * @return
     * @throws PaymentException
     */
    public void mntMbrBindSameRealCustNameAcct(String thirdUserId, String sellerThirdUserId) throws PaymentException {

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        //普通子账户的交易网会员昵称
//        requestParam.put("CommonSubAcctNickname", ApolloUtil.getProperty("pingan.accountNo"));
        //普通子账户的交易网会员代码
        requestParam.put("CommonSubAcctMemberCode", thirdUserId);
        //商户子账户的交易网会员昵称
//        requestParam.put("MerSubAcctNickname", ApolloUtil.getProperty("pingan.accountNo"));
        //商户子账户的交易网会员代码
        requestParam.put("MerSubAcctMemberCode", sellerThirdUserId);
        //监管账户的账号
        requestParam.put("FundSummaryAcctNo", ApolloUtil.getProperty("pingan.accountNo"));
        /**
         * 1. 申请开立商户子账户和普通会员子账户
         * 2. 申请为商户子账户开立关联的同名的普通会员子账户
         * 3. 申请为普通会员子账户开立关联的同名的商户子账户
         * 4. 查询两个会员子账户是否存在同名账户关系
         * 5. 维护两个子台帐信息一致的SH和00户为同名户关系
         */
        requestParam.put("FunctionFlag", "5");
        // 2.发送请求
        invoke(requestParam, PinganMethod.KFEJZB6293);

    }

    /**
     * 查询平安贷账户信息
     */
    public FinanceAccountInfoVo queryLoanAccountInfo(PinganLoanQueryApplyStatusParam param) throws PaymentException {
        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("ThirdApplyNo", param.getThirdApplyNo());
        requestParam.put("ProjectCode", ApolloUtil.getProperty("pingan.ProjectCode"));
        requestParam.put("ChannelCode", ApolloUtil.getProperty("pingan.ChannelCode"));

        // 2.请求体
        SdkRequest sdkRequest = new SdkRequest();
        sdkRequest.setInterfaceName("V1.0/" + PinganMethod.LOAN_APPLY_STATUS.getMethodName());
        sdkRequest.setBody(requestParam);

        // 3.发送请求
        log.info("PaymentLoanApiImpl#payFianceApplyStatus request:{}", JSONUtils.toJSON(requestParam));
        HttpResult response = apiClient.invoke(sdkRequest);
        log.info("PaymentLoanApiImpl#payFianceApplyStatus request:{} response:{}", JSONUtils.toJSON(requestParam), JSONUtils.toJSON(response));

        // 4.通信状态判断
        if(response == null || !response.isSuccess()) {
            log.error("PaymentLoanApiImpl#payFianceApplyStatus invoke pingan error, response isSuccess=false");
            throw new PaymentException(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
        }

        PinganAdapter.PinganResponse pinganResponse = PinganAdapter.PinganResponse.parse(response.getData());

        // 授信额度(单位分) 平安接口:单位：元，审批通过的时候才会返回
        String approveAmount = AmountCastUtil.castYuan2Fen(pinganResponse.getResultString("ApproveAmount")).toString();
        // 可用额度(单位分) 平安接口:单位：元，精确到分，建额成功的时候才会返回
        String availableAmount = AmountCastUtil.castYuan2Fen(pinganResponse.getResultString("AvailableAmount")).toString();
        log.info("PaymentLoanApiImpl.payFianceApplyStatus approveAmount {} availableAmount:{}", approveAmount, availableAmount);

        // 5.返回数据
        return FinanceAccountInfoVo.builder()
                .approveAmount(approveAmount)
                .thirdApplyNo(param.getThirdApplyNo())
                .availableAmount(availableAmount)
                .build();
    }

    public ResultVO<String> pushCustData(PinganLoanDatasToSendDto pinganLoanDatasToSendDto) {
        // 10.7 客户数据上送接口

        if (pinganLoanDatasToSendDto == null || StringUtils.isBlank(pinganLoanDatasToSendDto.getThirdApplyNo()) || pinganLoanDatasToSendDto.getSendType() == null) {
            return ResultVO.createError(ResultCodeEnum.PARAM_ERROR);
        }

        // 1.组装请求参数
        JSONObject requestParam = new JSONObject();
        requestParam.put("ThirdApplyNo", pinganLoanDatasToSendDto.getThirdApplyNo());
        requestParam.put("ProjectCode", ApolloUtil.getProperty("pingan.ProjectCode"));
        requestParam.put("ChannelCode", ApolloUtil.getProperty("pingan.ChannelCode"));
        requestParam.put("SendType", pinganLoanDatasToSendDto.getSendType().getCode());
        requestParam.put("IsMedicalLicenceRevoked", "N");
        JSONArray tableList = new JSONArray();
        requestParam.put("TableList", tableList);
        if (pinganLoanDatasToSendDto.getSendType() == PinganLoanDataSendTypeEnum.PRELOAN) {
            // 贷前
            putTableList(tableList, toMapListDataMerchantsInfo(pinganLoanDatasToSendDto.getDataMerchantsInfo()), PinganLoanDataTableNameEnum.MEDICINE_MERCHANTS_INFO_PRELOAN);
            putTableList(tableList, toMapListPinganLoanDataMonthlyOperation(pinganLoanDatasToSendDto.getDataMonthlyOperationList()), PinganLoanDataTableNameEnum.MEDICINE_MONTHLY_OPERATION_PRELOAN);
        } else {
            // 贷后
            putTableList(tableList, toMapListDataMerchantsInfo(pinganLoanDatasToSendDto.getDataMerchantsInfo()), PinganLoanDataTableNameEnum.MEDICINE_MERCHANTS_INFO_POSTLOAN);
            putTableList(tableList, toMapListPinganLoanDataMonthlyOperation(pinganLoanDatasToSendDto.getDataMonthlyOperationList()), PinganLoanDataTableNameEnum.MEDICINE_MONTHLY_OPERATION_POSTLOAN);
        }

        // 2.请求体
        SdkRequest sdkRequest = new SdkRequest();
        sdkRequest.setInterfaceName("V1.0/" + PinganMethod.LOAN_CUSTOMER_DATA.getMethodName());
        sdkRequest.setBody(requestParam);

        // 3.发送请求
        log.info("PaymentLoanApiImpl#payFianceSendCustomerData request:{}", JSONUtils.toJSON(requestParam));
        HttpResult response = apiClient.invoke(sdkRequest);
        log.info("PaymentLoanApiImpl#payFianceSendCustomerData request:{} response:{}", JSONUtils.toJSON(requestParam), JSONUtils.toJSON(response));

        // 4.通信状态判断
        if(response == null || !response.isSuccess()) {
            log.error("PaymentLoanApiImpl#payFianceSendCustomerData invoke pingan error, response isSuccess=false");
            return ResultVO.createError(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
        }

        PinganAdapter.PinganResponse pinganResponse = null;
        try {
            pinganResponse = PinganAdapter.PinganResponse.parse(response.getData());
            log.info("PaymentLoanApiImpl#payFianceSendCustomerData pinganResponse={}", JSON.toJSONString(pinganResponse));
        } catch (PaymentException e) {
            log.error("PaymentLoanApiImpl#payFianceSendCustomerData invoke pingan error", e);
            return ResultVO.createError(ResultCodeEnum.PINGAN_SYSTEM_ERROR);
        }
        return ResultVO.createSuccess();
    }

    private void putTableList(JSONArray tableList, List<Map<String,Object>> dataInfoMapList, PinganLoanDataTableNameEnum dataTableNameEnum) {

        JSONObject tableInfo = new JSONObject();
        tableInfo.put("TableName", dataTableNameEnum.getCode());
        tableInfo.put("DataList", JSON.toJSONString(dataInfoMapList));
        tableList.add(tableInfo);
    }

    private List<Map<String,Object>> toMapListDataMerchantsInfo(PinganLoanDataMerchantsInfo info) {
        List<Map<String,Object>> mapList = new ArrayList<>();
        Map<String,Object> map = new HashMap<>();
        map.put("PlatformNo",info.getPlatformNo());
        map.put("MerchantsName",info.getMerchantsName());
        map.put("CooperationYears",info.getCooperationYears());
        map.put("FixedBusinessPlace",info.getFixedBusinessPlace());
        map.put("IsRelatedCore",info.getIsRelatedCore());
        map.put("MerchantsType",info.getMerchantsType());
        map.put("CertificateExpireDate",info.getCertificateExpireDate());
        map.put("CertificateNo",info.getCertificateNo());
        map.put("IsMedicalLicenceRevoked","N");
        mapList.add(map);
        return mapList;
    }

    private List<Map<String,Object>> toMapListPinganLoanDataMonthlyOperation(List<PinganLoanDataMonthlyOperation> dataMonthlyOperationList) {
        List<Map<String,Object>> mapList = new ArrayList<>();
        for (PinganLoanDataMonthlyOperation info : dataMonthlyOperationList) {
            Map<String,Object> map = new HashMap<>();
            map.put("PlatformNo",info.getPlatformNo());
            map.put("Months",info.getMonths());
            map.put("MonthlyPurchaseAmount",info.getMonthlyPurchaseAmount());
            mapList.add(map);
        }
        return mapList;
    }
}
